Effacer le filtre
Article
Lorenzo Scalese · Mai 12, 2023
Selon le dictionnaire de Cambridge, tokéniser des données signifie "remplacer un élément de données privé par un jeton (= un élément de données différent qui représente le premier), afin d'empêcher que des renseignements privés soient vus par quelqu'un qui n'est pas autorisé à le faire" (https://dictionary.cambridge.org/pt/dicionario/ingles/tokenize). Aujourd'hui, plusieurs entreprises, en particulier dans les secteurs de la finance et de la santé, tokenisent leurs données, ce qui constitue une stratégie importante pour répondre aux exigences en matière de cybersécurité et de confidentialité des données ( RGPD, CCPA, HIPAA et LGPD). Mais pourquoi ne pas utiliser le chiffrement ? Le processus de tokenisation pour protéger les données sensibles est plus couramment utilisé que le chiffrement des données pour les raisons suivantes :
1. Amélioration des performances : le cryptage et le décryptage des données à la volée dans le cadre d'un traitement opérationnel intensif entraînent des performances médiocres et nécessitent une plus grande puissance du processeur.
2. Tests : il est possible de tokeniser une base de données de production et de la copier dans une base de données de test et de conserver les données de test adaptées à des tests unitaires et fonctionnels plus réels.
3. Meilleure sécurité : si un pirate informatique craque ou obtient la clé secrète, toutes les données cryptées seront disponibles, car le cryptage est un processus réversible. Le processus de tokenisation n'est pas réversible. Si vous devez récupérer les données d'origine à partir des données tokenisées, vous devez maintenir une base de données sécurisée et séparée pour relier les données d'origine et les données tokenisées.
## Architecture de tokenisation
L'architecture de tokenisation nécessite deux bases de données : l'App DB pour stocker les données tokenisées et d'autres données de l'entreprise et une base de données de jetons pour stocker les valeurs d'origine et tokenisées, de sorte que lorsque vous en avez besoin, votre application peut obtenir les valeurs d'origine à montrer à l'utilisateur. Il existe également une API REST Tonenizator qui permet de tokeniser les données sensibles, de les stocker dans la base de données de tokens et de renvoyer un ticket. L'application métier stocke le ticket, les données symbolisées et les autres données dans la base de données de l'application. Consultez le diagramme d'architecture :

## Application Tokenizator
Découvrez comment cela fonctionne dans l'application Tokenization : https://openexchange.intersystems.com/package/Tokenizator.
Cette application est une API REST qui permet de créer des jetons :
* Toute valeur est remplacée par les **étoiles**. Exemple : carte de crédit 4450 3456 1212 0050 par 4450 *\*\*\* \*\*** 0050.
* N'importe quelle **adresse IP réelle est remplacée par une fausse valeur**. Exemple : 192.168.0.1 par 168.1.1.1.
* Toute donnée **personne** est remplacée par une fausse personne. Exemple : Yuri Gomes avec l'adresse Brasilia, Brésil par Robert Plant avec l'adresse Londres, Royaume-Uni.
* Toute valeur **numéro** est remplacée par une fausse valeur numérique. Exemple : 300,00 par 320,00.
* Toute donnée de **carte de crédit** est remplacée par un faux numéro de carte de crédit. Exemple : 4450 3456 1212 0050 par 4250 2256 4512 5050.
* N'importe quelle valeur est remplacée par une valeur **hash**. Exemple : Architecte système par dfgdgasdrrrdd123.
* Toute valeur est remplacée par une expression **regex**. Exemple : EI-54105-tjfdk par AI-44102-ghdfg en utilisant la règle regex [A-Z]{2}-\d{5}-[a-z]{5}.
Si vous souhaitez une autre option, ouvrez une demande sur le projet github.
Pour tokeniser les valeurs et obtenir les valeurs d'origine par la suite, suivez les étapes suivantes :
1. Ouvrez votre Postman ou consommez cette API à partir de votre application.
2. Créez une demande de tokenisation en utilisant les méthodes STARS, PERSON, NUMBER, CREDITCARD, HASH, IPADDRESS et REGEX pour cet échantillon de données sensibles :
* Méthode : POST
* URL: http://localhost:8080/token/tokenize
* Corps (JSON):
[
{
"tokenType":"STARS",
"originalValueString":"545049405679",
"settings": {
"starsPosition":"1",
"starsQuantity":"4"
}
},
{
"tokenType":"IPADDRESS",
"originalValueString":"192.168.0.1",
"settings": {
"classType":"CLASS_B",
"ipSize":"4"
}
},
{
"tokenType":"PERSON",
"originalValueString":"Yuri Marx Pereira Gomes",
"settings": {
"localeLanguage":"en",
"localeCountry":"US",
"withAddress":"true",
"withEmail":"true"
}
},
{
"tokenType":"NUMBER",
"originalValueNumber":300.0,
"settings": {
"minRange":"100.0",
"maxRange":"400.0"
}
},
{
"tokenType":"CREDITCARD",
"originalValueString":"4892879268763190",
"settings": {
"type":"VISA"
}
},
{
"tokenType":"HASH",
"originalValueString":"Architecte système"
},
{
"tokenType":"REGEX",
"originalValueString":"EI-54105-tjfdk",
"settings": {
"regex":"[A-Z]{2}-\\d{5}-[a-z]{5}"
}
}
]
* Découvrez les résultats. Vous obtenez une valeur tokenizée (tokenizedValueString) à stocker dans votre base de données locale.
3. Copiez le ticket de la réponse (stockez-le dans votre base de données locale avec la valeur tokenisée).
4. Avec le ticket, vous pouvez maintenant obtenir la Valeur d'origine. Créez une demande pour obtenir la valeur d'origine en utilisant le ticket :
* Méthode: GET
* URL: http://localhost:8080/token/query-ticket/TICKET-VALUE-HERE
* Découvrez vos valeurs d'origine
Tous les tokens générés sont stockés dans SQL d'InterSystems IRIS Cloud pour vous permettre d'obtenir vos valeurs d'origine en toute performance et en toute confiance.
Profitez-en !
Article
Guillaume Rongier · Mai 2, 2022
La version 2021.2 de la plate-forme de données InterSystems IRIS Data Platform comprend de nombreuses nouvelles fonctionnalités intéressantes pour le développement rapide, flexible et sécurisé de vos applications critiques. Embedded Python est certainement la vedette (et pour une bonne raison !), mais en SQL, nous avons également fait un grand pas en avant vers un moteur plus adaptatif qui recueille des informations statistiques détaillées sur les données de votre tableau et les exploite pour fournir les meilleurs plans de requête. Dans cette brève série d'articles, nous allons examiner de plus près trois éléments qui sont nouveaux dans 2021.2 et qui travaillent ensemble vers cet objectif, en commençant par Run Time Plan Choice.
Il est difficile de trouver le bon ordre pour en parler (vous ne pouvez pas imaginer le nombre de fois où je les ai remaniés en rédigeant cet article), car ils s'emboîtent si bien les uns dans les autres. Vous pouvez donc les lire dans un ordre aléatoire .
Au sujet du traitement des requêtes IRIS
Lorsque vous soumettez une instruction au moteur IRIS SQL, celui-ci l'analyse dans une forme normalisée, en substituant tous les littéraux (paramètres de la requête), puis examine la structure de votre tableau, les indices et les statistiques sur les valeurs des champs afin de déterminer la stratégie d'exécution la plus efficace pour la requête normalisée. Cela permet au moteur de réutiliser le même plan et le même code généré lorsque vous souhaitez exécuter à nouveau la requête, éventuellement en utilisant des valeurs de paramètres de requête différentes. Par exemple, prenez la requête suivante :
SELECT * FROM Customer WHERE CountryCode = 'US' AND ChannelCode = 'DIRECT'
Cette requête sera normalisée sous une forme analogue à celle-ci :
SELECT * FROM Customer WHERE CountryCode = ? AND ChannelCode = ?
afin que les invocations ultérieures pour différentes combinaisons de pays et de canaux puissent immédiatement récupérer la même classe de requête mise en cache, ce qui permet d'éviter le travail de planification lourd en termes de calcul. Supposons que nous vendions des chaussures de course par l'intermédiaire d'une demi-douzaine de canaux et que nos produits soient vendus dans le monde entier ; en d'autres termes, les données sont réparties uniformément entre les valeurs possibles des champs CountryCode et ChannelCode. Si nous disposons d'un index régulier sur ces deux champs, le plan le plus efficace pour cette requête normalisée commencera par la condition la plus sélective (CountryCode), en utilisant l'indice correspondant pour accéder aux lignes correspondantes de la carte principale, puis vérifiera l'autre condition (ChannelCode) pour chaque ligne de la carte principale.
Valeurs aberrantes
Supposons que nous ne soyons pas un fabricant d'équipements sportifs, mais un vendeur spécialisé dans les moules pour chocolats belges (d'où est-ce que je sors ça ? ). Dans ce cas, supposons que la majorité de mes clients (disons 60 %) se trouvent en Belgique, ce qui signifie que "BE" devient une valeur "aberrante" pour le champ CountryCode, représentant un grand nombre de lignes de mon tableau. Soudain, l'indice sur le code pays a une autre utilité : _si_ le paramètre de requête pour CountryCode que nous obtenons au moment de l'exécution est 'BE', l'utilisation de cet index en premier signifierait que je devrais lire la majorité de ma carte principale, et qu'il serait préférable de commencer par l'index sur ChannelCode. Cependant, si la valeur du paramètre de requête pour CountryCode est une autre valeur, cela rendrait l'indice sur CountryCode beaucoup plus intéressant, car tous les autres pays se partagent les 40% de clients non belges restants.
C'est un exemple où vous voudriez choisir un plan différent au moment de l'exécution ; ou, en reformulant ces mots : **Run Time Plan Choice**. Le RTPC est un mécanisme qui ajoute un petit crochet dans la logique classique de substitution littérale et de recherche de requête en mémoire cache pour repérer les valeurs aberrantes telles que la valeur "BE" pour notre colonne CountryCode. Si vous souhaitez obtenir un aperçu plus détaillé du traitement des requêtes IRIS SQL, veuillez consulter [ce vidéo VS2020](https://learning.intersystems.com/course/view.php?id=1598).
Dans le passé, IRIS SQL supportait une version opt-in très rudimentaire de ce contrôle, mais la version 2021.2 introduit une toute nouvelle infrastructure RTPC, beaucoup plus légère et capable d'intervenir pour une plus grande variété de conditions de prédicat. Ayant établi que les frais généraux de cette vérification d'exécution sont effectivement minimes, nous avons décidé de l'activer par défaut, de sorte que vous n'avez rien à faire pour en bénéficier (à part [unfreeze vos plans de requête après la mise à niveau](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCRN_upgrade_intro#GCRN_upgrade_intro_frozen) comme d'habitude).
Nous avons souvent constaté à quel point les valeurs aberrantes sont fréquentes dans les ensembles de données du monde réel (et à quel point une distribution strictement uniforme est rare) et les tests sur un benchmark partenaire ont montré une amélioration spectaculaire des performances et des I/O, comme vous pouvez le voir dans le graphique ci-dessous. Nous avons également inclus les résultats du benchmark pour la version 2020.1, afin que vous puissiez apprécier nos efforts continus (et les résultats !) pour améliorer les performances au fil des versions.
Le temps de débit varie en fonction de la quantité de valeurs aberrantes dans votre ensemble de données et de la disponibilité des indices, mais nous sommes très enthousiastes quant au potentiel de ce changement et nous sommes très curieux de connaître vos expériences.
Article
Guillaume Rongier · Mai 3, 2022
Voici le deuxième article de notre série sur les améliorations apportées à la version 2021.2 de SQL, qui offre une expérience SQL adaptative et performante. Dans cet article, nous allons examiner les innovations en matière de collecte Table Statistics, qui sont bien sûr le principal élément d'entrée pour la capacité de Run Time Plan Choice que nous avons décrite dans l'article précédent.
Vous nous avez probablement entendu dire cela à plusieurs reprises : Tunez vos tables!
Pour ajuster vos tableaux à l'aide de la [commande SQL `TUNE TABLE`](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_tunetable) ou [`$SYSTEM.SQL.Stats.Table` ObjectScript API](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.SQL.Stats.Table#GatherTableStats) vous devez recueillir des statistiques sur les données de votre tableau pour aider IRIS SQL à élaborer un bon plan de requête. Ces statistiques comprennent des informations importantes telles que le nombre approximatif de lignes dans le tableau, ce qui aide l'optimiseur à décider de choses telles que l'ordre des JOIN (commencer par le tableau le plus petit est généralement plus efficace). De nombreux appels au support InterSystems concernant les performances des requêtes peuvent être résolus en exécutant simplement `TUNE TABLE` et en faisant un nouvel essai, car l'exécution de la commande invalidera les plans de requêtes existants afin que la prochaine invocation prenne en compte les nouvelles statistiques. D'après ces appels au support, nous voyons deux raisons récurrentes pour lesquelles ces utilisateurs n'avaient pas encore collecté les statistiques de tableaux : ils n'en connaissaient pas l'existence, ou ils ne pouvaient pas se permettre les frais d'exécution sur le système de production. Dans la version 2021.2, nous avons résolu ces deux problèmes.
Échantillonnage au niveau des blocs
Commençons par le second : le coût de la collecte des statistiques. Il est vrai que la collecte de statistiques sur les tables peut entraîner une quantité considérable d'entrées/sorties (I/O) et donc une surcharge si vous analysez l'ensemble de la table. L'API prenait déjà en charge l'échantillonnage seulement d'un sous-ensemble de lignes, mais cette opération avait tout de même la réputation d'être coûteuse. Dans la version 2021.2, nous avons modifié la situation pour ne plus sélectionner des lignes aléatoires en bouclant sur la globale de la carte principale, mais pour atteindre immédiatement le stockage physique sous-jacent et laisser le noyau prendre un échantillon aléatoire des blocs de base de données bruts pour cette globale. À partir de ces blocs échantillonnés, nous déduisons les lignes de tableau SQL qu'ils stockent et poursuivons avec notre logique habituelle de construction de statistiques par champ.
On peut comparer cela au fait de se rendre à un grand festival de la bière et, au lieu de parcourir toutes les allées et de choisir quelques stands de brasseries pour mettre une bouteille chacun dans son panier, de demander aux organisateurs de vous donner un casier avec des bouteilles prises au hasard et de vous épargner la marche (dans cette analogie de dégustation de bière, la marche serait en fait une bonne idée  ). Pour se calmer, voici un simple graphique représentant l'ancienne approche basée sur les lignes (croix rouges) par rapport à l'approche basée sur les blocs (croix bleues). Les avantages sont énormes pour les tableaux de grande taille, qui se trouvent être ceux sur lesquels certains de nos clients hésitaient à utiliser `TUNE TABLE`...
Il y a un petit nombre de limitations à l'échantillonnage par bloc, le plus important étant qu'il ne peut pas être utilisé sur des tables qui se trouvent en dehors des mappages de stockage par défaut (par exemple, en projetant à partir d'une structure globale personnalisée en utilisant `%Storage.SQL`). Dans de tels cas, nous reviendrons toujours à l'échantillonnage par ligne, exactement comme cela fonctionnait dans le passé.
Configuration automatique
Maintenant que nous avons résolu le problème de la perception de la surcharge, considérons l'autre raison pour laquelle certains de nos clients n'utilisaient pas `TUNE TABLE` : c'est qu'ils ne le savaient pas. Nous pourrions essayer de documenter notre façon de nous en sortir (et nous reconnaissons qu'il y a toujours de la place pour de meilleures documents), mais nous avons décidé que cet échantillonnage par blocs super efficace fournit en fait une opportunité de faire quelque chose que nous avons longtemps voulu : automatiser le tout. À partir de 2021.2, lorsque vous préparez une requête sur une table pour lequel aucune statistique n'est disponible, nous utiliserons d'abord le mécanisme d'échantillonnage par blocs ci-dessus pour collecter ces statistiques et les utiliser pour la planification de la requête, en sauvegardant les statistiques dans les métadonnées de la table afin qu'elles puissent être utilisées par les requêtes suivantes.
Si cela peut sembler effrayant, le graphique ci-dessus montre que ce travail de collecte de statistiques ne prend que quelques secondes pour les tables de la taille d'un Go. Si vous utilisez un plan de requête inapproprié pour interroger une telle table (en raison de l'absence de statistiques appropriées), cela risque d'être beaucoup plus coûteux que de procéder à un échantillonnage rapide dès le départ. Bien sûr, nous ne le ferons que pour les tables où nous pouvons utiliser l'échantillonnage par blocs et (malheureusement) nous procéderons sans statistiques pour ces tables spécialles qui ne supportent que l'échantillonnage par lignes.
Comme pour toute nouvelle fonctionnalité, nous sommes impatients de connaître vos premières expériences et vos commentaires. Nous avons d'autres idées concernant l'automatisation dans ce domaine, comme la mise à jour des statistiques en fonction de l'utilisation des tableaux, mais nous aimerions nous assurer que ces idées sont fondées sur les expériences acquises en dehors du laboratoire.
Article
Irène Mykhailova · Juin 28, 2023
Le code source de l'article est disponible à l'adresse suivante : https://github.com/antonum/ha-iris-k8s
Dans l'[article précédent](https://community.intersystems.com/post/highly-available-iris-deployment-kubernetes-without-mirroring), nous avons expliqué comment configurer IRIS sur un cluster k8s avec une haute disponibilité, basée sur le stockage distribué, au lieu de la mise en miroir traditionnelle. À titre d'exemple, cet article utilisait le cluster Azure AKS. Dans cet article, nous poursuivons l'exploration des configurations de haute disponibilité sur k8s. Cette fois, basée sur Amazon EKS (service Kubernetes géré par AWS) et incluant une option pour effectuer la sauvegarde et la restauration de la base de données, basée sur Kubernetes Snapshot.
## Installation
Passons tout de suite au travail. Tout d'abord, vous devez disposer d'un compte AWS et des outils [AWS CLI,](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) [kubectl](https://kubernetes.io/docs/tasks/tools/) et [eksctl](https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html). Pour créer le nouveau cluster, exécutez la commande suivante :
eksctl create cluster \
--name my-cluster \
--node-type m5.2xlarge \
--nodes 3 \
--node-volume-size 500 \
--region us-east-1
Cette commande prend ~15 minutes, elle déploie le cluster EKS et en fait un cluster par défaut pour votre outil kubectl. Vous pouvez vérifier le déploiement en exécutant :
kubectl obtenir les noeuds
NOM ÉTAT RÔLES AGE VERSION
ip-192-168-19-7.ca-central-1.compute.internal Prêt <néant> 18d v1.18.9-eks-d1db3c
ip-192-168-37-96.ca-central-1.compute.internal Prêt <néant> 18d v1.18.9-eks-d1db3c
ip-192-168-76-18.ca-central-1.compute.internal Prêt <néant> 18d v1.18.9-eks-d1db3c
L'étape suivante consiste à installer le moteur de stockage distribué Longhorn.
kubectl créer l'espace de nom longhorn-system
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.0/deploy/iscsi/longhorn-iscsi-installation.yaml --namespace longhorn-system
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml --namespace longhorn-system
Et enfin, l'IRIS lui-même :
kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml
À ce stade, vous aurez un cluster EKS entièrement fonctionnel avec le stockage distribué Longhorn et le déploiement IRIS installé. Vous pouvez revenir à l'article précédent et tenter de causer toutes sortes de dommages au cluster et au déploiement d'IRIS, juste pour voir comment le système se répare de lui-même. Consultez la section [Simuler la défaillance](https://community.intersystems.com/post/highly-available-iris-deployment-kubernetes-without-mirroring) section.
## Bonus n° 1 IRIS en ARM
IRIS EKS et Longhorn sont tous deux compatibles avec l'architecture ARM. Nous pouvons donc déployer la même configuration en utilisant les instances AWS Graviton 2, basées sur l'architecture ARM.
Il suffit de changer le type d'instance pour les nœuds EKS en famille 'm6g' et l'image IRIS en ARM.
eksctl créer cluster \
--name my-cluster-arm \
--node-type **m6g.2xlarge** \
--nodes 3 \
--node-volume-size 500 \
--region us-east-1
tldr.yaml
conteneurs:
#- image: store/intersystems/iris-community:2020.4.0.524.0
- image: store/intersystems/irishealth-community-arm64:2020.4.0.524.0
name: iris
Ou utilisez simplement :
kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr-iris4h-arm.yaml
Et voilà ! Vous avez obtenu votre cluster IRIS Kubernetes, fonctionnant sur la plateforme ARM.
## Bonus n°2 - Sauvegarde et restauration
Une partie souvent négligée de l'architecture niveau production est la capacité de créer des sauvegardes de votre base de données et de les restaurer rapidement et/ou de les cloner en cas de besoin.
Dans Kubernetes, la façon la plus courante de le faire est d'utiliser des instantanés de volumes persistants (Persistent Volume Snapshots).
Tout d'abord, vous devez installer tous les composants k8s requis :
#Installer CSI Snapshotter et CRDs
kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl apply -n kube-system -f https://github.com/kubernetes-csi/external-snapshotter/raw/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml
kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml
Ensuite, configurez les informations d'identification du seau S3 pour Longhorn (voir [instructions détaillées](https://longhorn.io/docs/1.1.0/snapshots-and-backups/backup-and-restore/set-backup-target/)):
#Le godet s3 cible de la sauvegarde Longhorn et les informations d'identification à utiliser par Longhorn pour accéder à ce godet.
#Voir https://longhorn.io/docs/1.1.0/snapshots-and-backups/backup-and-restore/set-backup-target/ pour les instructions d'installation manuelle
longhorn_s3_bucket=longhorn-backup-123xx #le nom du godet doit être unique au niveau global, à moins que vous ne souhaitiez réutiliser des sauvegardes et des informations d'identification existantes.
longhorn_s3_region=us-east-1
longhorn_aws_key=AKIAVHCUNTEXAMPLE
longhorn_aws_secret=g2q2+5DVXk5p3AHIB5m/Tk6U6dXrEXAMPLE
La commande suivante reprend les variables d'environnement de l'étape précédente et les utilise pour configurer la sauvegarde Longhorn.
#configurer la cible de sauvegarde Longhorn et les informations d'identification
cat <<EOF | kubectl apply -f -
apiVersion: longhorn.io/v1beta1
kind: Setting
metadata:
name: backup-target
namespace: longhorn-system
value: "s3://$longhorn_s3_bucket@$longhorn_s3_region/" # la cible de sauvegarde ici
---
apiVersion: v1
kind: Secret
metadata:
name: "aws-secret"
namespace: "longhorn-system"
labels:
data:
# echo -n '<secret>' | base64
AWS_ACCESS_KEY_ID: $(echo -n $longhorn_aws_key | base64)
AWS_SECRET_ACCESS_KEY: $(echo -n $longhorn_aws_secret | base64)
---
apiVersion: longhorn.io/v1beta1
kind: Setting
metadata:
name: backup-target-credential-secret
namespace: longhorn-system
value: "aws-secret" # nom secret de la sauvegarde ici
EOF
Cela peut sembler compliqué, mais cela indique en fait à Longhorn d'utiliser un godet S3 spécifique avec les informations d'identification spécifiées pour stocker le contenu des sauvegardes.
Voilà, c'est fait ! Si vous allez maintenant dans l'interface utilisateur de Longhorn, vous pourrez créer des sauvegardes, les restaurer, etc.

Voici un petit rappel sur la façon de se connecter à l'interface utilisateur Longhorn :
kubectl get pods -n longhorn-system
# noter le nom complet du pod pour le pod 'longhorn-ui-...'
kubectl port-forward longhorn-ui-df95bdf85-469sz 9000:8000 -n longhorn-system
Cela permettrait de transférer le trafic vers Longhorn UI sur votre site http://localhost:9000.
## Sauvegarde/restauration programmatique
Effectuer une sauvegarde et une restauration via l'interface utilisateur Longhorn peut être une première étape suffisante - mais nous ferons un pas en avant et effectuerons la sauvegarde et la restauration de manière programmatique, en utilisant les API Snapshot de k8s.
Tout d'abord, l'instantané lui-même. iris-volume-snapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshot
metadata:
name: iris-longhorn-snapshot
spec:
volumeSnapshotClassName: longhorn
source:
persistentVolumeClaimName: iris-pvc
Cet instantané de volume fait référence au volume source 'iris-pvc' que nous utilisons pour notre déploiement IRIS. Il suffit donc de l'appliquer pour lancer immédiatement le processus de sauvegarde.
C'est une bonne idée d'exécuter la fonction de Gel/Dégel du démon d'écriture d'IRIS avant/après l'instantané.
#Gel du démon d'écriture
echo "Gel du démon d'écriture d'IRIS"
kubectl exec -it -n $namespace $pod_name -- iris session iris -U%SYS "##Class(Backup.General).ExternalFreeze()"
status=$?
if [[ $status -eq 5 ]]; then
echo "IRIS WD EST CONGELÉ, exécution de la sauvegarde"
kubectl apply -f backup/iris-volume-snapshot.yaml -n $namespace
elif [[ $status -eq 3 ]]; then
echo "ÉCHEC DU GEL DE L'IRIS WD"
fi
#Dégel du démon d'écriture
kubectl exec -it -n $namespace $pod_name -- iris session iris -U%SYS "##Class(Backup.General).ExternalThaw()"
Le processus de restauration est assez simple. Il s'agit essentiellement de créer un nouveau PVC et de spécifier l'instantané comme source.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: iris-pvc-restored
spec:
storageClassName: longhorn
dataSource:
name: iris-longhorn-snapshot
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Il suffit ensuite de créer un nouveau déploiement, basé sur ce PVC. Regardez ce [script de test dans un référentiel github](https://github.com/antonum/ha-iris-k8s/blob/main/backup/test.sh) qui se déroulerait de manière séquentielle :
* Créer un nouveau déploiement d'IRIS
* Ajouter des données à IRIS
* Geler le démon d'écriture, prendre un instantané, dégeler le démon d'écriture
* Créer un clone du déploiement d'IRIS, basé sur l'instantané.
* Vérifier que toutes les données sont toujours présentes
À ce stade, vous aurez deux déploiements IRIS identiques, l'un étant un clone par sauvegarde de l'autre.
Profitez-vous-en bien !
Article
Lorenzo Scalese · Juin 1, 2022
## Un système de stockage global d'aspect plus industriel
Dans le premier article de cette série, nous avons étudié le modèle entité-attribut-valeur (EAV) dans les bases de données relationnelles, et nous avons examiné les avantages et les inconvénients du stockage de ces entités, attributs et valeurs dans des tables. Nous avons appris que, malgré les avantages de cette approche en termes de flexibilité, elle présente de réels inconvénients, notamment une inadéquation fondamentale entre la structure logique des données et leur stockage physique, qui entraîne diverses difficultés.
Pour résoudre ces problèmes, nous avons décidé de voir si l'utilisation de globales - qui sont optimisées pour le stockage d'informations hiérarchiques - serait efficace pour les tâches que l'approche EAV traite habituellement.
Dans la [Partie 1](https://fr.community.intersystems.com/post/mod%C3%A8le-entit%C3%A9-attribut-valeur-dans-les-bases-de-donn%C3%A9es-relationnelles-faut-il-%C3%A9muler-les), nous avons créé un catalogue pour une boutique en ligne, d'abord en utilisant des tables, puis en utilisant une seule globale. Maintenant, essayons d'implémenter la même structure pour quelques globales.
Dans la première globale, `^catalog`, nous allons stocker la structure du répertoire. Dans la deuxième globale, `^good`, nous allons stocker les marchandises. Et dans la globale `^index`, nous allons stocker les index. Puisque nos propriétés sont liées à un catalogue hiérarchique, nous ne créerons pas de globale séparée pour elles.
Avec cette approche, pour chaque entité (à l'exception des propriétés), nous avons une globale séparée, ce qui est bon du point de vue de la logique. Voici la structure du catalogue global :
.png)
Set ^сatalog(root_id, "Properties", "capacity", "name") = "Capacity, GB"
Set ^сatalog(root_id, "Properties", "capacity", "sort") = 1
Set ^сatalog(root_id, sub1_id, "Properties", "endurance", "name") = "Endurance, TBW"
Set ^сatalog(root_id, sub1_id, "Properties", "endurance", "sort") = 2
Set ^сatalog(root_id, sub1_id, "goods", id_good1) = 1
Set ^сatalog(root_id, sub1_id, "goods", id_good2) = 1
Set ^сatalog(root_id, sub2_id, "Properties", "avg_seek_time", "name") = "Rotate speed, ms"
Set ^сatalog(root_id, sub2_id, "Properties", "avg_seek_time", "sort") = 3
Set ^сatalog(root_id, sub2_id, "goods", id_good3) = 1
Set ^сatalog(root_id, sub2_id, "goods", id_good4) = 1
Une globale avec des marchandises ressemblera à quelque chose comme ceci :

Set ^good(id_good, property1) = value1
Set ^good(id_good, property2) = value2
Set ^good(id_good, property3) = value3
Set ^good(id_good, "catalog") = catalog_id
Bien sûr, nous avons besoin d'index afin que pour toute section du catalogue contenant des marchandises, nous puissions trier par les propriétés dont nous avons besoin. Une globale d'index aura une structure semblable à quelque chose comme ceci :

Configurer ^index(id_catalog, property1, id_good) = 1
; Pour obtenir rapidement le chemin complet du sous-catalogue concret
Configurer ^index("path", id_catalog) = "^catalog(root_id, sub1_id)"
Ainsi, dans n'importe quelle section du catalogue, on peut obtenir une liste triée. Une globale d'index est facultative. Il n'est utile que si le nombre de produits dans cette section du catalogue est important.
## Code ObjectScript pour travailler avec des données de démonstration Demo Data
Maintenant, nous allons utiliser ObjectScript pour travailler avec nos données. Pour commencer, nous allons obtenir les propriétés d'une marchandise spécifique. Nous avons l'ID d'une marchandise particulière et nous devons afficher ses propriétés dans l'ordre donné par la valeur de tri. Voici le code pour cela :
get_sorted_properties(path, boolTable)
{
; mémoriser toutes les propriétés dans la globale temporaire
While $QLENGTH(@path) > 0 {
if ($DATA(@path("Properties"))) {
set ln=""
for {
Set ln = $order(@path("Properties", ln))
Quit: ln = ""
IF boolTable & @path("Properties", ln, "table_view") = 1 {
Set ^tmp(@path("Properties", ln, "sort"), ln) = @path("Properties", ln, "name")
}
ELSE {
Set ^tmp(@path("Properties", ln, "sort"), ln) = @path("Properties", ln, "name")
}
}
}
}
print_sorted_properties_of_good(id_good)
{
Set id_catalog = ^good(id_good, "catalog")
Set path = ^index("path", id_catalog)
Do get_sorted_properties(path, 0)
set ln =""
for {
Set ln = $order(^tmp(ln))
Quit: ln = ""
Set fn = ""
for {
Set fn = $order(^tmp(ln, fn))
Quit: fn = ""
Write ^tmp(ln, fn), " ", ^good(id_good, fn),!
}
}
}
Ensuite, nous voulons récupérer les produits de la section catalogue sous la forme de la table, basé sur `id_catalog` :
print_goods_table_of_catalog(id_catalog)
{
Set path = ^index("path", id_catalog)
Do get_sorted_properties(path, 1)
set id=""
for {
Set id = $order(@path("goods"), id)
Quit: id = ""
Write id," ", ^good(id, "price"), " "
set ln =""
for {
Set ln = $order(^tmp(ln))
Quit: ln = ""
Set fn = ""
for {
Set fn = $order(^tmp(ln, fn))
Quit: fn = ""
Write ^tmp(ln, fn), " ", ^good(id, fn)
}
Write !
}
}
}
## Lisibilité : EAV SQL contre les globales
Comparons maintenant l'utilisation d'EAV et de SQL par rapport à l'utilisation de globales. En ce qui concerne la clarté du code, il est évident qu'il s'agit d'un paramètre subjectif. Mais regardons, par exemple, la création d'un nouveau produit.
Nous allons commencer par l'approche EAV, en utilisant SQL. Tout d'abord, nous devons obtenir une liste des propriétés de l'objet. Il s'agit d'une tâche distincte qui prend beaucoup de temps. Supposons que nous connaissions déjà les IDs de ces trois propriétés : `capacité`, `poids`, et `endurance`.
START TRANSACTION
INSERT INTO good (name, price, item_count, catalog_id) VALUES ('F320 3.2TB AIC SSD', 700, 10, 15);
SET @last_id = LAST_INSERT_ID ();
INSERT INTO NumberValues Values(@last_id, @id_capacity, 3200);
INSERT INTO NumberValues Values(@last_id, @id_weight, 0.4);
INSERT INTO NumberValues Values(@last_id, @id_endurance, 29000);
COMMIT
Dans cet exemple, nous n'avons que trois propriétés, et l'exemple ne semble donc pas si inquiétant. Dans le cas général, nous aurions toujours quelques insertions dans la table de texte à l'intérieur de la transaction :
INSERT INTO TextValues Values(@last_id, @ id_text_prop1, 'Text value of property 1');
INSERT INTO TextValues Values(@last_id, @ id_text_prop2, 'Text value of property 2');
...
INSERT INTO TextValues Values (@last_id, @id_text_propN, 'Text value of property N');
Bien sûr, nous pourrions simplifier un peu la version SQL si nous utilisions la notation textuelle à la place des propriétés ID, par exemple "capacité" au lieu d'un nombre. Mais dans le monde SQL, ce n'est pas acceptable. Il est plutôt d'usage d'utiliser un ID numérique pour énumérer les instances d'entités. Cela permet d'obtenir des index plus rapides (il faut indexer moins d'octets), il est plus facile de suivre l'unicité et il est plus facile de créer automatiquement un nouvel ID. Dans ce cas, le fragment d'insertion aurait l'apparence suivante :
INSERT INTO NumberValues Values(@last_id, 'capacity', 3200);
INSERT INTO NumberValues Values(@last_id, 'weight', 0.4);
INSERT INTO NumberValues Values(@last_id, 'endurance', 29000);
Voici le même exemple en utilisant des globales :
TSTART
Set ^good(id, "name") = "F320 3.2TB AIC SSD"
Set ^("price") = 700, ^("item_count") = 10, ^("reserved_count") = 0, ^("catalog") = id_catalog
Set ^("capacity") = 3200, ^("weight") = 0.4, ^("endurance") = 29000
TCOMMIT
Supprimons maintenant une marchandise en utilisant l'approche EAV :
START TRANSACTION
DELETE FROM good WHERE id = @ good_id;
DELETE FROM NumberValues WHERE good_id = @ good_id;
DELETE FROM TextValues WHERE good_id = @ good_id;
COMMIT
Et ensuite, faisons la même chose avec les globales :
Kill ^good(id_good)
Nous pouvons également comparer les deux approches en termes de longueur de code. Comme vous pouvez le constater dans les exemples précédents, lorsque vous utilisez des globales, le code est plus court. C'est une bonne chose. Plus le code est court, moins il y a d'erreurs et plus il est facile à comprendre et à gérer.
En général, un code plus court est aussi plus rapide. Et, dans ce cas, c'est certainement vrai, puisque les globales constituent une structure de données de niveau inférieur aux tables relationnelles.
## Mise à l'échelle des données avec EAV et Globales
Ensuite, examinons la mise à l'échelle horizontale. Avec l'approche EAV, nous devons au moins distribuer les trois plus grandes tables sur les serveurs : Good, NumberValues, et TextValues. Les tables contenant des entités et des attributs peuvent simplement être entièrement copiés sur tous les serveurs, car ils contiennent peu d'informations.
Dans chaque serveur, avec une mise à l'échelle horizontale, des produits différents seraient stockés dans les tables Good, NumberValues et TextValues. Nous devrions allouer certains blocs d'identification pour les produits sur chaque serveur afin d'éviter la duplication des identifiants pour des produits différents.
Pour une mise à l'échelle horizontale avec des globales, il faudrait configurer des plages d'ID dans la globale et attribuer une plage de globale à chaque serveur.
La complexité est à peu près la même pour EAV et pour les globales, sauf que pour l'approche EAV, nous devrions configurer des plages d'ID pour trois tables. Avec les globales, nous configurons les ID pour une seule globale. C'est-à-dire qu'il est plus facile d'organiser la mise à l'échelle horizontale pour les globales.
## Perte de données avec EAV et avec Globales
Enfin, considérons le risque de perte de données dû à des fichiers de base de données corrompus. Où est-il plus facile de sauvegarder toutes les données : dans cinq tables ou dans trois globales ( y compris une globale d'index ) ?
Je pense que c'est plus facile dans trois globales. Avec l'approche EAV, les données des marchandises différentes sont mélangées dans des tables, alors que pour les globales, les informations sont stockées de manière plus holistique. Les branches sous-jacentes sont stockées et triées séquentiellement. Par conséquent, la corruption d'une partie de la globale est moins susceptible d'entraîner des dommages que la corruption de l'une des tables dans l'approche EAV, où les données sont stockées comme des pâtes entremêlées.
Un autre casse-tête dans la récupération des données est l'affichage des informations. Avec l'approche EAV, les informations sont réparties entre plusieures tables et des scripts spéciaux sont nécessaires pour les assembler en un seul ensemble. Dans le cas des globales, vous pouvez simplement utiliser la commande ZWRITE pour afficher toutes les valeurs et les branches sous-jacentes du nœud.
## Les Globales d'InterSystems IRIS : Une meilleure approche ?
L'approche EAV est apparue comme une astuce pour stocker des données hiérarchiques. Les tables n'ont pas été conçus à l'origine pour stocker des données imbriquées. L'approche EAV de facto est l'émulation des globales dans les tables. Étant donné que les tables représentent une structure de stockage de données de plus haut niveau et plus lente que les globales, l'approche EAV échoue par rapport aux globales.
À mon avis, pour les structures de données hiérarchiques, les globales sont plus pratiques et plus compréhensibles en termes de programmation, tout en étant plus rapides.
Si vous avez prévu une approche EAV pour votre projet, je vous suggère d'envisager d'utiliser les globales d'InterSystems IRIS pour stocker les données hiérarchiques.
Article
Guillaume Rongier · Août 29, 2022
Dans la première partie, j'ai montré comment démarrer un nouveau projet sur Django, ainsi que définir de nouveaux modèles et ajouter des modèles existants.
Cette fois, je vous présenterai un panneau d'administration, qui est disponible dès le départ, et comment il peut être utile.
_Remarque importante : ne vous attendez pas à ce que si vous essayez de répéter les actions de cet article, cela fonctionnera pour vous, ce n'est pas le cas. Au cours de l'article, j'ai dû faire quelques corrections dans le projet django-iris, et même dans le pilote DB-API fait par InterSystems pour corriger certains problèmes là aussi, et je pense que ce pilote est encore en développement, et nous aurons un pilote plus stable dans le futur. Considérons que cet article ne fait qu'expliquer comment cela pourrait être si nous avions tous fait._
Revenons à notre code et voyons ce que nous avons dans urls.py, qui est le principal point d'entrée de toutes les requêtes web.
"""main URL Configuration
La liste `urlpatterns` dirige les URLs vers les visualisations. Pour plus d'informations, veuillez consulter :
https://docs.djangoproject.com/en/4.0/topics/http/urls/
Exemples :
Visualisations des fonctions
1. Ajoutez un import : from my_app import views
2. Ajouter une URL à urlpatterns : path('', views.home, name='home')
Visualisations basées sur des classes
1. Ajoutez un import : from other_app.views import Home
2. Ajouter une URL à urlpatterns : path('', Home.as_view(), name='home')
En incluant une autre URLconf
1. Importez la fonction include() : from django.urls import include, path
2. Ajoutez une URL à urlpatterns : path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
Comme vous pouvez le constater, l'URL **_admin_** y est déjà définie
Lançons le serveur de développement, par la commande
python manage.py runserver
Si vous passez par l'URL _http://localhost:8000/admin_, vous trouverez le formulaire de connexion à l'administration de Django
.png)
Pour entrer ici, nous avons besoin d'un utilisateur, et nous pouvons le créer avec cette commande
$ python manage.py createsuperuser
Nom d'utilisateur (laissez vide pour utiliser 'daimor') : admin
Adresse e-mail : admin@example.com
Mot de passe :
Mot de passe ( de nouveau) :
Le mot de passe est trop similaire au nom d'utilisateur.
Ce mot de passe est trop court. Il doit contenir au moins 8 caractères.
Ce mot de passe est trop courant.
Contourner la validation du mot de passe et créer un utilisateur quand même ? [O/N] : O
Le superutilisateur a été créé avec succès.
Nous pouvons maintenant utiliser ce nom d'utilisateur et ce mot de passe. Il est assez vide pour le moment, mais il donne déjà accès aux groupes et aux utilisateurs.
.png)
## Plus de données
Précédemment j'ai déjà installé le paquet post-and-tags avec zpm, vous pouvez le faire aussi
zpm "install posts-and-tags"
Maintenant nous pouvons obtenir les modèles pour tous les tableaux (_community.post, community.comment, community.tag_) installés par ce paquet
$ python manage.py inspectdb community.post community.comment community.tag > main/models.py
Le fichier sera un peu long, donc, je l'ai mis dans un spoiler
main/models.py
# This is an auto-generated Django model module.
# Vous devrez effectuer les opérations suivantes manuellement pour nettoyer tout cela :
# * Réorganiser les modèles' order
# * Assurez-vous que chaque modèle a un champ avec primary_key=True
# * Assurez-vous que chaque ForeignKey et OneToOneField a `on_delete` réglé sur la valeur désirée behavior
# * Supprimez les lignes `managed = False` si vous souhaitez autoriser Django à créer, modifier et supprimer la table
# N'hésitez pas à renommer les modèles, mais ne renommez pas les valeurs de db_table ou les noms de champs.
from django.db import models
class CommunityPost(models.Model):
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.TextField(db_column='Text', 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.
class Meta:
managed = False
db_table = 'community.post'
class CommunityComment(models.Model):
id1 = models.CharField(db_column='ID1', primary_key=True, max_length=62) # 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.
correct = models.BooleanField(db_column='Correct', blank=True, null=True) # Nom du champ en minuscules.
created = models.DateTimeField(db_column='Created', 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.
id = models.IntegerField(db_column='Id') # Nom du champ en minuscules.
post = models.CharField(db_column='Post', max_length=50, blank=True, null=True) # Nom du champ en minuscules.
text = models.TextField(db_column='Text', blank=True, null=True) # Nom du champ en minuscules.
texthash = models.CharField(db_column='TextHash', max_length=50, blank=True, null=True) # Nom du champ en minuscules.
type = models.CharField(db_column='Type', max_length=50) # Nom du champ en minuscules.
votesamount = models.IntegerField(db_column='VotesAmount', blank=True, null=True) # Nom du champ en minuscules.
class Meta:
managed = False
db_table = 'community.comment'
unique_together = (('type', 'id'),)
class CommunityTag(models.Model):
description = models.TextField(db_column='Description', blank=True, null=True) # Nom du champ en minuscules.
name = models.TextField(db_column='Name', primary_key=True) # Nom du champ en minuscules.
class Meta:
managed = False
db_table = 'community.tag'
Le tableau de bord d'administration en Django peut être étendu par le développeur. Il est ainsi possible d'ajouter des tableaux supplémentaires. Pour ce faire, nous devons ajouter un nouveau fichier nommé **main/admin.py.** J'ai ajouté quelques commentaires dans le code, pour expliquer certaines lignes.
from django.contrib import admin
# Importation de nos modèles communautaires pour nos tableaux dans IRIS
from .models import (CommunityPost, CommunityComment, CommunityTag)
# classe de registre qui remplace le comportement par défaut du modèle CommunityPost
@admin.register(CommunityPost)
class CommunityPostAdmin(admin.ModelAdmin):
# liste des propriétés à afficher dans le tableau view
list_display = ('posttype', 'name', 'publisheddate')
# liste des propriétés pour lesquelles il faut afficher un filtre à la droite du tableau
list_filter = ('posttype', 'lang', 'published')
# ordre par défaut, c'est-à-dire à partir de la date la plus récente de PublishedDate
ordering = ['-publisheddate', ]
@admin.register(CommunityComment)
class CommunityCommentAdmin(admin.ModelAdmin):
# seuls ces deux champs apparaissent, ( le post est numérique par id dans le tableau post)
list_display = ('post', 'created')
# classées par date de création
ordering = ['-created', ]
@admin.register(CommunityTag)
class CommunityTagAdmin(admin.ModelAdmin):
# pas tellement à montrer
list_display = ('name', )
## Portail d'extension
Retournons à notre page d'administration de Django, et examinons les nouveaux éléments qui y ont été ajoutés
.png)
Sur le côté droit, vous verrez le panneau de filtrage, et ce qui est important, c'est qu'il contient toutes les valeurs possibles dans des champs particuliers et qu'il les affiche.
_Malheureusement, InterSystems SQL ne permet pas d'utiliser les fonctions LIMIT, OFFSET, attendues d'une manière ou d'une autre par Django. Par ailleurs, Django ne permet pas d'utiliser la fonction TOP. Ainsi, la pagination sera affichée ici mais ne fonctionnera pas. Et il n'y a aucun moyen de la faire fonctionner pour le moment, et je ne pense pas que cela fonctionnera un jour, malheureusement._
Vous pouvez même plonger dans l'objet, et Django affichera le formulaire, avec les types de champs corrects. ( _remarque : Cet ensemble de données ne contient pas de données dans le champ Texte_)
.png)
.png)
Objet du commentaire
.png)
Problèmes avec les licences à prévoir sur Community Edition
Si vous travaillez avec Community Edition, vous pouvez être confronté à ce problème. Voici à quoi cela ressemble lorsque toutes les connexions sont occupées, et cela peut arriver très vite. Et si vous voyez que la réponse de votre serveur est assez longue, c'est probablement le cas, car IRIS ne répond pas rapidement dans ce cas, pour des raisons inconnues il prend beaucoup de temps.
Même quand IRIS dit qu'il vous reste plus des places
Il n'autorise pas plus de 5 connexions, il faut donc terminer un ou plusieurs processus pour que cela fonctionne, ou redémarrer le serveur Django
Dans le cadre du développement, vous pouvez également limiter le serveur Django à un mode sans threads, afin qu'il fonctionne dans un seul processus. Et il ne devrait pas obtenir plus de connexions à IRIS
python manage.py runserver --nothreading
Article
Lorenzo Scalese · Sept 5, 2022
# Introduction
Cet article vise à donner un aperçu du service FHIR Accelerator Service (FHIRaaS) piloté par l'implémentation de l'application iris-on-fhir, disponible dans OEX développé pour le concours FHIRaaS.
Un tutoriel de base vous guidera dans la configuration d'un déploiement FHIRaaS fonctionnel, comprenant une clé API et un serveur OAuth 2.0.
Une bibliothèque permettant d'utiliser les ressources FHIR par le biais de FHIRaaS est également brièvement évoquée.
Enfin, certaines fonctionnalités de l'application iris-on-fhir sont présentées dans des articles séparés. Vous pouvez consulter le code complet sur le référentiel github de l'application.
Ce contenu sera présenté dans une série de 3 artciles.
Ce premier article semble un peu gros, mais ne vous inquiétez pas, c'est parce que j'ai mis beaucoup d'images pour vous aider dans vos étapes de configuration.
# FHIRaaS
IRIS fournit déjà un environnement API FHIR [intégré dans IRIS for Health et IRIS Health Connect](https://www.intersystems.com/fhir/#our-products-support-fhir).
Mais si vous souhaitez profiter de l'environnement fiable, sécurisé et à faible maintenance offert par les services en nuage, vous pouvez désormais compter sur [InterSystems IRIS FHIR Accelerator Service (FHIRaaS)](https://docs.intersystems.com/components/csp/docbook/Doc.View.cls?KEY=FAS_intro).
FHIRaaS est une infrastructure FHIR prête à l'emploi basée sur des services en nuage. Il vous suffit de mettre en place un déploiement et de commencer à utiliser l'API FHIR dans vos applications, quelle que soit leur nature : client JS (SMART on FHIR), backend traditionnel ou applications sans serveur.
Pour demander votre essai gratuit de FHIRaaS, veuillez contacter InterSystems.
## Configuration du déploiement
Après la connexion, cliquez sur le bouton "CREATE NEW DEPLOYMENT".

Vous devrez passer par quelques étapes. La première consiste à choisir la taille du déploiement. Au moment de la rédaction de cet article, FHIRaaS ne propose qu'une seule option. Il suffit donc d'appuyer sur le bouton "CONTINUE".

L'étape suivante consiste à choisir le fournisseur de services en nuage qui sera utilisé. Là encore, une seule option était disponible au moment de la rédaction de cet article : AWS.

La dernière configuration est juste le nom du déploiement. Il y a quelques règles pour ce nom, et l'interface FHIRaaS vous alerte lorsqu'un nom invalide est fourni. Notez également que vous ne pouvez pas changer ce nom après la configuration. Cliquez sur le bouton "CONTINUE" après avoir sélectionné le nom.

Enfin, révisez votre configuration et démarrez votre déploiement FHIRaaS en cliquant sur le bouton "CREATE".

Si tout se passe bien, vous recevrez un message sympa indiquant que votre déploiement FHIRaaS est en cours de développement. Attendez quelques minutes jusqu'à la fin de ce processus.

Après quelques minutes, votre déploiement FHIRaaS est prêt à être utilisé. Il suffit d'appuyer sur le bouton de déploiement et de commencer à l'utiliser.

Après avoir cliqué sur le bouton de déploiement, l'onglet "Aperçu" est présenté. Notez que vous disposez de plusieurs onglets. Dans cet article, seuls les onglets "OAuth 2.0", "Credentials" et "API Development" seront couverts. Mais il ne s'agit que d'un aperçu, les autres ne sont pas du tout compliqués et vous pouvez facilement les explorer.

## Contrôle d'accès
FHIRaaS prend en charge deux méthodes de contrôle d'accès : Clé API et OAuth 2.0. Passons en revue chacune d'entre elles.
### Clé d'API
Les clés API sont des jetons générés par FHIRaaS qui permettent à vos applications d'interagir avec l'API sans interaction avec l'utilisateur. Cette méthode est donc idéale pour la communication entre le serveur FHIRaaS et les applications tierces autorisées, comme une API de chatbot, par exemple.
Pour créer une clé API, accédez d'abord à l'onglet "Credentials", puis cliquez sur le bouton "CRÉER UNE CLÉ API".

Choisissez un nom pour votre clé API et cliquez sur le bouton "ADD".

Génial ! Votre clé API a été créée ! Copiez-la et enregistrez-la dans un endroit sûr - vous ne pourrez plus accéder à cette information.

Après la création, vous ne pouvez que supprimer une clé API.

Pour utiliser cette clé Api, il suffit d'ajouter un en-tête x-api-key à une requête HTTP. Par exemple :
```
curl -X GET "https://fhir.lrwvcusn.static-test-account.isccloud.io/Patient" -H "accept: application/fhir+json" -H "x-api-key: your-apy-key"
```
### OAuth 2.0 - Création d'un serveur OAuth 2.0 et ajout d'utilisateurs à celui-ci
Comme nous l'avons dit précédemment, la clé API permet d'utiliser l'API sans interaction avec l'utilisateur. Mais, lorsque vous créez une application pour vos utilisateurs, OAuth 2.0 et OpenID Connect sont aujourd'hui la norme industrielle pour l'authentification et l'autorisation.
Une petite remarque : bien que vous puissiez utiliser OAuth 2.0 et OpenID Connect indépendamment, il est tout à fait normal de voir les deux fonctionner côte à côte. Ainsi, lorsque je parle d'OAuth 2.0, je veux dire OAuth 2.0 pour l'autorisation et OpenID Connect pour l'authentification.
Alors, configurons un serveur OAuth 2.0. Tout d'abord, sélectionnez l'onglet "OAuth 2.0", puis cliquez sur le bouton "CREATE AUTHENTICATION SERVER"

L'étape suivante consiste à choisir un nom pour votre serveur OAuth 2.0 et à sélectionner le fournisseur d'identité (IdP) à utiliser. Au moment où cet article a été écrit, FHIRaaS ne supportait que AWS Cognito comme IdP. Il suffit donc d'appuyer sur le bouton "CREATE".

Si votre demande a été réussie, un message vous sera envoyé, comme dans l'image ci-dessous. Maintenant vous pouvez ajouter des utilisateurs à votre IdP en cliquant sur le bouton "ADD USER".

Vous serez redirigé vers l'onglet "Credentials". Pour ajouter un utilisateur, cliquez sur le bouton "CREATE USER".

Saisissez le nom d'utilisateur et son mot de passe, puis cliquez sur le bouton "CREER".

Si tout se passe bien, vous pouvez maintenant voir un utilisateur créé dans votre IdP. Cet utilisateur peut maintenant se connecter à des applications autorisées par ce serveur OAuth 2.0.

### OAuth 2.0 - Ajout d'applications au serveur OAuth 2.0
Après la création d'un serveur OAuth 2.0 et l'ajout d'utilisateurs à celui-ci, ces utilisateurs peuvent utiliser les applications autorisées par ce serveur. Maintenant, nous allons ajouter une application au serveur.
Tout d'abord, accédez à l'onglet "OAuth 2.0", sélectionnez "Application", puis cliquez sur le bouton "CREATE APPLICATION".

Ensuite, choisissez un nom pour votre application dans le serveur et le serveur OAuth 2.0 qui vient d'être créé. Les URLs doivent diriger vers votre application. L'"URL de redirection" est l'adresse de destination lorsque les utilisateurs se connectent avec succès. L'"URL de déconnexion" est la page vers laquelle les utilisateurs sont redirigés, lorsqu'ils utilisent l'IdP pour se déconnecter.

Vous pouvez rediriger vers localhost pendant le développement, mais, bien sûr, pour la production, vous devez fournir une URL accessible par l'Internet.
Les dernières étapes consistent à choisir les ressources FHIR (domaines d'application) que les utilisateurs doivent accepter de partager avec l'application. Pour ce test simple, toutes les ressources sont demandées, mais dans les applications réelles, vous pouvez contrôler chaque ressource FHIR, comme si l'application pouvait juste lire, juste écrire ou les deux. Si les utilisateurs ne sont pas d'accord avec cette demande d'autorisation, le serveur OAuth 2.0 refusera l'accès à ces ressources.
Après avoir correctement configuré les domaines, appuyez sur le bouton "CREER".

Si tout se passe bien, vous verrez apparaître un message vert. Maintenant, vous pouvez vérifier les paramètres de votre nouvelle application ou la supprimer en appuyant sur la case de l'application. Vous pouvez également créer plusieurs applications dont vous avez besoin.

## Développement d'API
Parmi les fonctionnalités intéressantes de FHIRaaS, citons l'onglet Développement API. Il vous fournit un explorateur de spécification OpenAPI de l'API FHIR, vous permettant d'essayer facilement toutes les fonctionnalités FHIR.
Pour y accéder, cliquez sur l'onglet "API Development". Après son chargement, vous pouvez sélectionner la ressource FHIR que vous souhaitez explorer. Notez que FHIRaaS fournit la version R4 pour les ressources FHIR.

Maintenant, nous allons nous authentifier pour utiliser l'outil. Vous devez d'abord créer une clé API.

Bien, maintenant, nous allons avoir tous les patients dans cette instance de FHIRaaS :

Comme vous pouvez le voir dans l'animation ci-dessus, vous pouvez effectuer toutes les opérations CRUD sur la ressource Patient - de même pour toutes les autres ressources disponibles.
Ce qui est bien ici, c'est que vous n'avez pas besoin de connaître toute la structure des ressources pour essayer d'effectuer des opérations sur elles. Par exemple, si vous souhaitez créer un nouveau patient, l'outil vous fournit un modèle pour cette ressource :

Vous disposez de la même fonctionnalité pour les autres ressources FHIR.
À la fin de la page, l'outil vous offre une belle vue de toutes les ressources liées, sous forme de schémas :

# Conclusion
Dans cet article, nous abordons certains aspects de FHIRaaS et mettons en place un déploiement fonctionnel
Dans le prochain article, nous examinerons quelques exemples simples de son utilisation dans des applications.
Pour suivre l'actualité, ouvrez l'annonce!
Article
Guillaume Rongier · Mai 6, 2022
Voici le troisième article de notre courte série sur les innovations d'IRIS SQL qui offrent une expérience plus adaptative et plus performante aux analystes et aux applications requérant des données relationnelles sur IRIS. Il s'agit peut-être du dernier article de cette série pour 2021.2, mais nous prévoyons plusieurs autres améliorations dans ce domaine. Dans cet article, nous allons approfondir un peu plus les statistiques de tableaux supplémentaires que nous commençons à rassembler dans cette version : Histogrammes
Que signifie le mot "histogramme" ?
Un histogramme est une représentation approximative de la distribution des données d'un champ numérique (ou de manière plus générale des données qui ont un ordre précis). Il est utile de connaître la valeur la plus petite, la plus grande et la moyenne d'un tel champ, mais cela ne vous dit pas grand-chose sur la façon dont les données sont réparties entre ces trois points. C'est là qu'intervient l'histogramme, qui divise la plage de valeurs en "godets" et compte le nombre de valeurs de champ qui apparaissent dans chaque godet. Il s'agit d'une définition assez souple et vous pouvez toujours choisir de prendre la taille des godets de telle sorte que les godets soient également "larges" en termes de valeurs de champ, ou également "larges" en termes de nombre de valeurs échantillonnées couvertes. Dans ce dernier cas, chaque godet contient le même pourcentage de valeurs et les godets représentent donc des percentiles. Le graphique ci-dessus trace un histogramme pour le champ EventData de l'ensemble de données [Aviation Demo dataset](https://github.com/intersystems/Samples-Aviation), en utilisant la même largeur de godet exprimée en nombre de jours.
Pourquoi aurais-je besoin d'un histogramme ?
Supposons que vous cherchiez dans cet ensemble de données tous les événements antérieurs à 2004 dans l'État de Californie :
SELECT * FROM Aviation.Event WHERE EventDate < '2004-05-01' AND LocationCountry = 'California'
Dans notre article précédent sur [Choix du plan d'exécution](https://fr.community.intersystems.com/post/20212-fonctionnalit%C3%A9-sql-en-vedette-choix-du-plan-dex%C3%A9cution), nous avons déjà discuté comment capturer la sélectivité et les valeurs aberrantes potentielles pour un champ comme LocationCountry dans les statistiques de la table. Mais de telles statistiques pour les valeurs de champs individuels ne sont pas très pratiques pour cette condition `<` sur EventDate. Pour calculer la sélectivité de cette condition, vous devez agréger la sélectivité de toutes les valeurs possibles de EventDate jusqu'au 1er mai 2004, ce qui peut être une requête assez exigeante en soi plutôt que le genre d'estimation rapide que vous pouvez vous permettre au moment de la planification de la requête. C'est là que les histogrammes sont utiles.
Reprenons notre histogramme pour la distribution des valeurs EventDate, en divisant cette fois les données en 16 sections de même taille, chacune contenant 6,667 % des données. De cette façon, les choses se traduisent plus facilement en percentiles et en nombres de sélectivité que nous pouvons utiliser pour les estimations du coût des requêtes. Pour lire ce tableau, regardons la quatrième ligne : 20 % des valeurs (3 godets de 6,667 % chacun) précèdent la limite inférieure de ce godet du 22 juin 2003, et il contient 6,667 % de valeurs supplémentaires, jusqu'au 19 septembre 2003.
Godet
Percentile
Valeur
0%
21/12/2001
1
7%
02/07/2002
2
13%
19/01/2003
3
20%
22/06/2003
4
27%
19/09/2003
5
33%
30/12/2003
6
40%
01/10/2004
7
47%
01/10/2005
8
53%
20/08/2006
9
60%
14/01/2007
10
67%
02/04/2008
11
73%
14/05/2008
12
80%
29/11/2008
13
87%
01/06/2010
14
93%
30/10/2011
15
100%
26/09/2012
La date de coupure utilisée dans l'exemple de requête ci-dessus (1er mai 2004) se trouve dans le cinquième godet, et comporte entre 33 % et 40 % des valeurs précédant cette date. Au fur et à mesure que les godets deviennent plus petits, nous pouvons considérer que la distribution _à l'intérieur_ de ceux-ci est approximativement uniforme et simplement interpoler entre les limites inférieure et supérieure, ce qui dans ce cas conduit à une sélectivité d'environ 37%, que nous pouvons utiliser dans notre estimation du coût de la requête.
Voici une autre façon de visualiser notre utilisation des histogrammes, en les traçant comme un barre de distribution cumulative. Nous pouvons voir comment la ligne tracée pour le 1er mai 2004 sur l'axe X (les valeurs), se traduit par ~37% sur l'axe Y.
L'exemple ci-dessus utilise une condition d'intervalle avec juste une limite supérieure pour plus de clarté, mais l'approche fonctionne évidemment aussi bien lorsqu'on utilise une limite inférieure ou une condition d'intervalle (par exemple en utilisant le prédicat BETWEEN).
À partir de la version 2021.2, nous collectons des histogrammes dans le cadre des statistiques de tables pour tout champ organisé, y compris les chaînes de caractères, et nous les utilisons pour estimer la sélectivité des plages dans le cadre du RTPC. De nombreuses requêtes du monde réel impliquent une condition de plage sur les champs de date (et autres). Nous sommes donc convaincus que cette amélioration d'IRIS SQL sera bénéfique à la planification des requêtes pour bon nombre de nos clients et, comme toujours, nous sommes impatients de connaître vos expériences.
Article
Iryna Mykhailova · Mai 5, 2022
Quand on travaille avec les globales, on voit qu’il n’y a pas mantes fonction en ObjectScript (COS) à utiliser. C’est aussi le cas avec Python et Java. Toutefois, toutes ses fonctions sont indispensables quand on travaille directement avec les données sans utilisation des objets, des documents ou des tables.
Dans cet article je voudrais parler de différentes fonctions et commandes qui se servent à travailler avec les globales dans trois langues : ObjectScript, Python et Java (les deux derniers en utilisant Native API).
Avant de commencer par les commandes et fonctions qui s’utilisent pour travailler avec les globales, à mon avis, c’est important de discourir sur le sujet des globales en général. Qu'est-ce que c'est, la globale ?
En accord avec la documentation officielle, une globale (ou une variable globale) est une variable qui permet de stocker des données dans la base de données et de les rendre disponibles à tous les processus accédant à cette base de données. Pour signaler qu'une variable est globale, vous devez utiliser la syntaxe suivante :
^a
où a est le nom de la variable et ^ est le symbole qui signifie que cette variable doit être stockée dans la base de données.
La variable peut avoir des indices ou être sans eux. Dans l'exemple précédent, la variable n’a pas des indices. Si on veut les ajouter, on doit le faire en les écrivant dans les parenthèses comme ça:
^a(ind1, ind2, ...)
ou ind1 et ind2 sont les indices de la variable a.
Quand on parle des globales il est important de connaître les points suivants :
Une globale consiste en un ensemble de nœuds (dans certains cas, un seul nœud), identifiés par des indices.
Chaque nœud peut contenir une valeur.
InterSystems IRIS fournit des fonctionnalités pour itérer à travers les nœuds d'un global et accéder rapidement aux valeurs (et on va discuter de ces fonctionnalités plus tard).
Une globale est automatiquement stocké dans la base de données. Lorsque vous affectez une valeur à un nœud d'une variable globale, les données sont écrites immédiatement dans la base de données.
Vous pouvez voir le contenu d'une globale par programmation ou sur le Management Portal.
Vous pouvez aussi lire plus sur les globales dans la série des articles sur la Communauté des Développeurs.
Pour travailler avec les globales en Python et Java on doit utiliser la Native API pour ces langages. En général, les Native APIs sont des interfaces légères qui permettent d'accéder directement aux globales. Et pour y accéder, premièrement, vous devez configurer votre environnement.
Configuration pour Python :
Téléchargez InterSystems IRIS Native API en accord avec votre OS (s’il y a de nécessité, renommera le fichier, par exemple pour Windows et Python 3.10 ça doit être irisnative-1.0.0-py3-none-any.whl)
Installez ce wheel :
Pip install irisnative-1.0.0-py3-none-any.whl
Emportez le module :
import irisnative
Ajoutez le code pour ouvrir la connexion et créer l’objet iris
args = {'hostname':'127.0.0.1',
'port':51773,
'namespace':'USER',
'username':'_SYSTEM',
'password':'SYS'}
conn = irisnative.createConnection(**args)
iris = irisnative.createIris(conn)
Après tout est fini, n’oubliez pas à fermer la connexion et l’objet IRIS en utilisant le code ci-dessous :
conn.close()
iris.close()
Configuration pour Java :
Ajoutez intersystems-jdbc-XXX.jar dans votre projet, où XXX est la version liée à votre version de Java. Vous pouvez trouver ce fichier dans le dossier <installation>/dev/java/lib/
Emportez les modules suivants :
import com.intersystems.jdbc.IRISConnection;
import com.intersystems.jdbc.IRIS;
import com.intersystems.jdbc.IRISIterator;
Dans votre code ajoutez le code pour ouvrir la connexion et créer l’objet IRIS :
String connStr = "jdbc:IRIS://<server>:<port>/<namespace>";
//par exemple "jdbc:IRIS://127.0.0.1:51773/USER"
String user = "_SYSTEM";
String pwd = "SYS";
IRISConnection conn = (IRISConnection)java.sql.DriverManager.getConnection(connStr,user,pwd);
IRIS iris = IRIS.createIRIS(conn);
Après tout est fini, n’oubliez pas à fermer la connexion et l’objet IRIS en utilisant le code ci-dessous :
iris.close();
conn.close();
Maintenant que nous sommes familiarisés avec l’info de base sur les globales et comment configurer l’environnement, allons regarder sur les fonctions et commandes pour travailler avec eux en ObjectScript (COS), Python et Java.
Nom de fonction (commande) et sa description
COS syntaxe
COS exemples
Python syntaxe
Python exemples
Java syntaxe
Java exemples
set - attribue une valeur à une variable
set:pc argument, ...
où pc est post condition (facultative) et argument peut être soit
variable = valeur
soit
(liste de variables) = valeur
set ^z = 648
//attribue une valeur 648 à une globale ^z
set ^a = 10, ^b(“dif”) = 21, ^b(10) = 8
//attribute une valeur 10 à une globale ^a, une valeur 21 à une globale ^b(“dif”) et une valeur 8 à une globale ^b(10)
set:^a=10 ^c = “string”
//attribue une valeur “string” à une globale ^c si une globale ^a a une valeur 10
set (^x, ^y) = “string”
//attribue une valeur “string” aux globales ^x et ^y
iris.set(value, globalName, *subscripts)
où value est la valeur de datatype supportée, globalName est le nom de la globale et subscripts sont la liste des indices (une ou zéro des indices)
# attribue une valeur "A" à une globale ^root("foo", "SubFoo"), la même chose que set ^root("foo", "SubFoo") = "A"
iris.set('A', 'root', 'foo', 'SubFoo')
# attribue une valeur 123 à une globale ^root("bar", "lowbar", "UnderBar"), la même chose que set ^root("bar", "lowbar", "UnderBar") = 123
iris.set(123, 'root', 'bar', 'lowbar', 'UnderBar')
# attribue une valeur 963 à une globale ^root, la même chose que set ^root = 123
iris.set(963, 'root')
void jdbc.IRIS.set(value, globalname, subscripts)
où value est la valeur de datatype supportée, globalname est le nom de la globale et subscripts sont la liste des indices (une ou zéro des indices)
iris.set("A", "root", "foo", "SubFoo");
//attribue une valeur "A" à une globale ^root("foo", "SubFoo"), la même chose que set ^root("foo", "SubFoo") = "A"
iris.set(123, "root", "bar", "lowbar", "UnderBar");
//attribue une valeur 123 à une globale ^root("bar", "lowbar", "UnderBar"), la même chose que set ^root("bar", "lowbar", "UnderBar") = 123
iris.set(963, "root");
//attribue une valeur 963 à une globale ^root, la même chose que set ^root = 963
increment – augmente la global spécifié avec la valeur passée. S'il n'y a pas de nœud à l'adresse spécifiée, un nouveau nœud est créé avec la valeur passée.
$increment(variable, num)
où variable est la variable la valeur de laquelle doit être changée et num est l'incrément numérique que vous souhaitez ajouter (facultatif, par défaut = 1)
set ^b=$increment(^a)
//^b =11
set ^c=$increment(^a, -3)
//^c = 7
iris.increment(value, globalName, *subscripts)
où value est la valeur numérique, globalName est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices)
iris.increment(-2, 'myGlobal', 'B')
# diminue la valeur du nœud ^myGlobal("B") par 2
long increment(value, globalname, subscripts)
où value est la valeur numérique, globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices)
//diminue la valeur du nœud ^myGlobal("B") par 2
iris.increment(-2, "myGlobal", "B");
get - obtient une valeur de la globale
$get(variable, defaut)
où variable est la variable la valeur de laquelle on veut obtenir et defaut est la valeur à renvoyer si la variable n'est pas définie (facultative).
set val = $get(^b(“dif”))
//val = 21
set val = $get(^b(1), 100)
//val = 100
iris.get(globalName, *subscripts)
iris.getBoolean(globalName, *subscripts)
iris.getBytes(globalName, *subscripts)
iris.getFloat(globalName, *subscripts)
iris.getLong(globalName ,*subscripts)
iris.getString(globalName, *subscripts)
où globalName est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices).
# obtient la valeur string de la globale ^root("foo", "SubFoo")
subfoo = iris.getString('root', 'foo', 'SubFoo')
# obtient la valeur de la globale ^root("bar", "lowbar", "UnderBar")
underbar = iris.get('root', 'bar', 'lowbar', 'UnderBar')
Boolean jdbc.IRIS.getBoolean(globalname, subscripts)
byte[] jdbc.IRIS.getBytes(globalname, subscripts)
Date jdbc.IRIS.getDate(globalname, subscripts)
Double jdbc.IRIS.getDouble(globalname, subscripts)
Float jdbc.IRIS.getFloat(globalname, subscripts)
InputStream jdbc.IRIS.getInputStream(globalname, subscripts)
Integer jdbc.IRIS.getInteger(globalname, subscripts)
Long jdbc.IRIS.getLong(globalname, subscripts)
Object jdbc.IRIS.getObject(globalname, subscripts)
Reader jdbc.IRIS.getReader(globalname, subscripts)
Short jdbc.IRIS.getShort(globalname, subscripts)
String jdbc.IRIS.getString(globalname, subscripts)
Time jdbc.IRIS.getTime(globalname, subscripts)
Timestamp jdbc.IRIS.getTimestamp(globalname, subscripts)
IRISList jdbc.IRIS.getIRISList(globalname, subscripts)
où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices). Si le nœud n’existe pas la fonction retourne null.
String subfoo = iris.getString("root", "foo", "SubFoo");
//obtient la valeur de la globale ^root("foo", "SubFoo")
Integer underbar = iris.getInteger("root");
//obtient la valeur de la globale ^root
Object obj = iris.getObject("root", "nothing");
//dans ce cas obj = null
kill – supprime des variables avec tous leurs nœuds descendants
kill:pc argument, ...
où pc est post condition (facultative) et argument (facultatif) peut être soit
variable, ...
soit
(variable, ...)
kill ^a
//supprime une globale ^a avec tous leurs nœuds descendants
kill ^b, ^c
//supprime des globales ^b et ^c avec tous leurs nœuds descendants
kill:^a=10 ^z
//supprime une globale ^z avec tous leurs nœuds descendants si une globale ^a a une valeur 10
iris.kill(globalName, *subscripts)
où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices)
iris.kill('myGlobal', 'A')
# supprime la variable ^myGlobal('A') et tous ses nœuds descendants
jdbc.IRIS.kill(globalName, subscripts)
où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices)
kill("root", "foo", "SubFoo") ;
//supprime la variable ^root('foo', 'SubFoo') et tous ses nœuds descendants
zkill:pc nœud, ...
où pc est post condition (facultative) et nœud est le noud exact de la globale
zkill ^b(“dif”)
//supprime un nœud ^b(“dif”), laisse tous les autres nœuds
data/isDefined – renvoie une valeur indiquant si le nœud spécifié existe et s'il contient une valeur. Elle peut renvoyer quatre valeurs : 0 — le nœud spécifié n'existe pas ; 1 — le nœud existe et a une valeur ; 10 — le nœud est sans valeur mais a des sous-nœuds ; 11 — le nœud a à la fois une valeur et des sous-nœuds.
$data(variable, target)
où variable est la variable a vérifier et target est la variable où on renvoie la valeur actuelle de la variable (facultative)
set val = $data(^a)
//val = 1
set val = $data(^b)
//val = 10
set val = $data(^qqch)
//val = 0
iris.isDefined(globalName, *subscripts)
où globalName est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices)
if iris.isDefined('root')%10 > 0:
print(iris.get('root'))
# si le nœud ^root a de valeur, l’imprime
int jdbc.IRIS.isDefined(globalName, subscripts)
où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices)
//si le nœud ^root("foo") n’existe pas
if isDefined("root", "foo") ==0 {
//crée le nœud ^root("foo") avec la valeur 74
iris.set(74, "root", "foo");
}
iterator - itère sur un ensemble de sous-nœuds.
$order(variable, direction, target)
où variable est la variable à partir de laquelle commence le parcours en largeur, direction est l’ordre croissant (1, par défaut) ou décroissant (-1) dans lequel on parcourt l’arbre (facultatif) et target est la variable où on renvoie la valeur actuelle de la variable trouvée (facultative)
set mydata(1) = "a", mydata(-3) = "C", mydata(5) = "e", mydata(-5) = "E"
set key = $order(mydata(""))
while (key'="") {
write key, ", "
// obtient le nouvel indice
set key = $order(mydata(key))
}
// -5, -3, 1, 5,
iris.iterator(globalName, *subscripts) - renvoie un objet iterator pour le nœud spécifié,
où globalName est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices)
# reçoit l’iterateur
subscript_iter = iris.iterator('root')
for subscript, value in subscript_iter:
# imprime l’indice et la valeur des sous-nœuds
print("subscript = {}, value = {}".format(subscript, value))
IRISIterator getIRISIterator(globalName, subscripts) - renvoie un objet IRISIterator pour le nœud spécifié,
où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices)
//reçoit l’iterateur
IRISIterator iter = iris.getIRISIterator("myNames","people");
//vérifie si le nœud suivant existe
while (iter.hasNext()) {
//cherche le nœud suivant
iter.next();
//imprime la valeur d’indice et de nœud
System.out.print(" \"" + iter.getSubscriptValue() + "\"=" + iter.getValue());
};
iterator.items() – renvoi l’iterateur qui contient les indices et les valeurs
boolean hasNext() – renvoie true si l'itérateur a un élément suivant
$qsubscript(namevalue, intexpr)
où namevalue est le string avec le nom de variable et intexpr est un numéro qui spécifie le nom à renvoyer : nom de l'espace de noms (-1), nom de la variable(0) ou nom de l'indice(1-..)
set ^grocerylist("food","fruit",1)="apples"
write $qsubscript($name(^grocerylist("food","fruit",1)), 0)
//^grocerylist
write $qsubscript($name(^grocerylist("food","fruit",1)), 1)
//food
write $qsubscript($name(^grocerylist("food","fruit",1)), 2)
//fruit
write $qsubscript($name(^grocerylist("food","fruit",1)), 3)
//1
iterator.values() – renvoi l’iterateur qui contient juste les valeurs
boolean hasPrevious() – renvoie true si l'itérateur a un élément précèdent
iterator.subscripts() – renvoi l’iterateur qui contient juste les indices
String next() – renvoie l’élément suivant
$qlength(var)
où var est le string avec le nom de variable dont la fonction compte le nombre d'indices
write $qlength($name(^grocerylist("food","fruit",1)))
//3
iterator.reversed() – renvoi l’iterateur dans la direction opposée de la précédente
String previous() – renvoie l’élément suivant
//reçoit l’iterateur
IRISIterator iter = iris.getIRISIterator("myNames","people");
//vérifie si le nœud précèdent existe
while (iter.hasPrevious()) {
//cherche le nœud précédent
iter.previous();
//imprime la valeur de l’indice et du nœud
System.out.print(" \"" + iter.getSubscriptValue() + "\"");
};
iterator.next() – renvoi l’indice, la valeur ou tous les deux du nœud suivant (ou précèdent)
# reçoit l’iterateur
iterNode= iris.iterator('root').subscripts()
output = "\nSubscripts under node root: "
try:
while True:
output += '%s ' % iterNode.next()
except StopIteration:
print(output + '\n')
# pour finir le boucle
Object getValue() – obtient la valeur du nœud à la position actuelle de l'itérateur
$query(reference, direction, target)
où reference est la variable à partir de laquelle commence le parcours en profondeur, direction est l’ordre croissant (1, par défaut) ou décroissant (-1) dans lequel on parcour l’arbre (facultatif) et target est la variable où on renvoie la valeur actuelle de la variable trouvée (facultative)
set ^a = 10
set ^a(10) = 100
set ^a("Bonjour") = 200
set ^a(10, "Salut") = 300
set ^a(10, 150) = 80
set x = "^a"
for {
set x = $query(@x)
quit:x=""
write x, ", "
}
//^a(10), ^a(10,150), ^a(10,"Salut"), ^a("Bonjour"),
iterator.startFrom(subscript) – renvoi l’iterateur qui commence par le nœud subscript
void startFrom(subscript) – définit la position de départ de l'itérateur à l'indice spécifié
où subscript est un point de départ arbitraire et n'a pas besoin de spécifier un nœud existant
void remove() – supprime de la collection sous-jacente le dernier élément renvoyé par cet itérateur
String getSubscriptValue() – obtient l'indice de niveau le plus bas pour le nœud à la position actuelle de l'itérateur
merge – copie la source dans la destination et tous les descendants de la source dans les descendants de la destination.
merge:pc arg, ...
où pc est post condition (facultative) et arg est
destination = source
set ^a = 10
set ^a(10) = 100
set ^a("Bonjour") = 200
set ^a(10, "Salut") = 300
set ^a(10, 150) = 80
set ^b = 25
set ^b(10) = 248
set ^b(10, 48) = 963
merge ^a("Bonjour") = ^b(10)
//copie les nœuds descendants de ^b(10) dans le nœud ^a("Bonjour")
merge = on recoit
*Tous les exemples utilisent le code présent dans les exemples précédents dans la même colonne.
J'espère que ce tableau vous aidera à développer votre propre code qui travaille directement avec les globales. Vous pouvez lire plus sur l’utilisation des globales dans la Documentation Native API pour Python et Native API pour Java. Si vous avez des questions, n'hésitez pas à les poser dans les commentaires.
Article
Sylvain Guilbaud · Juil 10, 2023
Découvrir Django
Django est un framework web conçu pour développer des serveurs et des API, et pour traiter des bases de données de manière rapide, évolutive et sécurisée. Pour ce faire, Django fournit des outils permettant non seulement de créer le squelette du code, mais aussi de le mettre à jour sans souci. Il permet aux développeurs de voir les changements presque en direct, de corriger les erreurs avec l'outil de débogage et de traiter la sécurité avec facilité.
Pour comprendre le fonctionnement de Django, examinons l'image :
En résumé, le client envoie son requête avec une URL. Cette URL est adressée à une vue qui doit traiter cette requête de manière appropriée en récupérant les informations du modèle alimenté par la base de données. Une fois que le traitement est achevé, la réponse est renvoyée au client par l'intermédiaire d'un modèle. Django rend ce processus complexe moins problématique.
Découvrir Django-iris
Django utilise des backends personnalisables pour gérer n'importe quelle base de données dont le développeur pourrait avoir besoin. MySQL, Postgre, Oracle et d'autres ont déjà été implémentés et sont faciles à trouver sur GitHub. Django-iris est un backend permettant d'adapter le framework Django aux bases de données IRIS, de sorte que toutes les fonctionnalités des plates-formes d'InterSystems peuvent être utilisées en parallèle avec les outils Django.
Pratiquement, si nous reprenons l'illustration précédente, la BASE DE DONNÉES serait IRIS au lieu de PostgreSQL.
Le code source et des informations sur l'application et ses développeurs peuvent être trouvés sur django-iris par caretdev.
Pour commencer
J'ai appris à Innovatium que lorsqu'il s'agit d'un nouveau langage de code ou d'un nouvel outil, la meilleure façon de commencer est de découvrir comment réaliser un système CRUD (abbréviation anglaise de "Create, Read, Update, and Delete", c'est-à-dire créer, lire, mettre à jour et supprimer) simple. Après cela, il sera plus facile de comprendre comment cela fonctionne et d'évoluer vers des tâches plus complexes et plus spécifiques que vous pourriez être amené à accomplir.
Compte tenu de ce qui précède, je vais d'abord vous montrer comment construire le CRUD le plus simple. Après avoir clarifié les choses, et lorsque vous commencerez à vous familiariser avec l'outil, je passerai à d'autres exemples où Django peut être utile. Enfin, si nous avons de la chance, je pourrai vous convaincre de l'utiliser.
Vous pouvez suivre ce tutoriel sur l'historique des commits du référentiel GitHub, en commençant par django-admin startproject irisCrud.
Conditions préalables
Vous devez avoir Python, Django, un bon outil de développement comme VSCode, et un accès à une instance d'IRIS. Voici un guide rapide :
Download VSCode (n'oubliez pas d'ajouter des extensions Python pour vous aider à développer)
Download Python
Tapez pip install django dans le terminal
Tapez pip install django-iris dans le terminal
Le coup d'envoi "Kick-off"
Créez un dossier pour votre projet, placez le répertoire de votre terminal dans ce nouveau dossier et tapez django-admin startproject PROJECTNAME .
Vous pouvez ignorer le point à la fin pour créer un dossier à l'intérieur du répertoire courant avec le nom PROJECTNAME.
Cela devrait créer les fichiers suivants, dans lesquels vous devez choisir "CRUD" comme PROJECTNAME :
Vous pouvez maintenant effectuer votre première migration et lancer votre serveur en tapant les commandes suivantes dans le terminal. À ce stade, vous devriez remarquer que quelques fichiers supplémentaires ont été créés.
python manage.py migrate
python manage.py runserver
Si vous suivez le lien [http://127.0.0.1:8000/](http://127.0.0.1:8000/), vous verrez la page d'accueil par défaut des projets Django.La prochaine étape consistera à créer le superutilisateur avec python manage.py createsuperuser. Soyez prudent : lorsque vous saisissez le mot de passe, vous ne verrez aucun changement, mais rappelez-vous que le terminal lit déjà ce que vous tapez.
Ajouter des exigences pour django-iris
Créez un fichier dans le dossier racine, nommez-le requirements.txt, et copiez-y ce qui suit :
# Django itself
django>=4.0.0
# InterSystems IRIS driver for Django
django-iris==0.2.2
Dans le terminal, tapez `pip install -r requirements.txt` et obtenez votre django-iris prêt à être utilisé dans votre projet.Ouvrez maintenant settings.py et cherchez la configuration DATABASES. Modifiez-la pour obtenir quelque chose de similaire à ce qui suit :
DATABASES = {
'default': {
'ENGINE': 'django_iris',
'NAME': 'USER',
'USER': '_SYSTEM',
'PASSWORD': 'SYS',
'HOST': 'localhost',
'PORT': 1972,
}
}
Où
ENGINE doit pointer sur 'django_iris' ;
NAME doit indiquer l'espace de noms souhaité ;
USER et PASSWORD doivent se référer à un utilisateur disposant de privilèges adéquats ;
HOST et PORT doivent mener à votre instance. Vous pouvez le vérifier dans le gestionnaire de serveur InterSystems IRIS.
Création d'une application
Retournez dans le terminal et tapez python manage.py startapp NAMEOFTHEAPP.
Vous devriez obtenir un dossier similaire au dossier ci-dessous :
Vous pouvez maintenant créer un modèle en modifiant models.py et en créant une classe avec le nom et les champs requis. Par exemple :
class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Book</span><span class="hljs-params">(models.Model)</span>:
title = models.CharField(max_length=class="hljs-number">50</span>)
author = models.CharField(max_length="hljs-number">30)
Chaque champ doit être spécifié en tant que propriété "models". Vous pouvez consulter tous les champs disponibles [ici](https://docs.djangoproject.com/en/4.2/ref/models/fields/). Vous pouvez également définir diverses options, telles que le traitement des valeurs nulles et vides ou la création d'alternatives possibles pour le champ.Pour s'assurer que le programme reconnaîtra cette nouvelle application, il faut retourner à settings.py et ajoutez le nom de l'application à la liste INSTALLED_APPS :
INSTALLED_APPS: [
…
‘books’,
]
Une fois l'étape précédente terminée, saisissez python manage.py makemigrations dans le terminal pour configurer les futures migrations. Vérifiez migrations/__pycache__/0001_initial.py Pour mieux comprendre ce le fonctionnement de cette commande. Ensuite, saisissez python manage.py migrate pour appliquer les changements.
Passez dans le portail de gestion IRIS > Explorateur de système > SQL, choisissez le même espace de noms que celui que vous avez défini dans les paramètres DATABASES et recherchez SQLUser. Vous verrez les nouvelles tableaux créés dans IRIS :
Si vous choisissez un schéma dans le tableau, vous pourrez voir son nom de classe qui peut être ouvert et modifié (à vos risques et périls) à partir du Studio ou de tout autre outil de développement.
Un peu de front-end
Si vous ouvrez le fichier urls.py à partir du dossier du projet (CRUD), vous remarquerez un chemin préconstruit qui mène à admin. Si vous suivez http://127.0.0.1:8000/admin, il vous sera demandé d'entrer un nom d'utilisateur et un mot de passe. Vous pouvez utiliser celui que vous avez sélectionné lors de l'exécution de la commande createuperuser et jeter un coup d'œil à ce que Django a à offrir sur cette page.
Cependant, nous pourrions avoir plus de pages. Pour créer une URL personnalisée basée sur la nouvelle application, importez include de django.urls et ajoutez un nouveau chemin, pointant vers l'application :
class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path, include
urlpatterns = [
path("hljs-string">'admin/', admin.site.urls),
path("hljs-string">'books/', include("hljs-string">'books.urls')),
]
Cette opération devrait vous rediriger vers le fichier books/urls.py (il vous faudra d'abord créer ce fichier), dans lequel nous allons ajouter un autre chemin nous permettant d'accéder à la vue. Le fichier devrait ressembler à ce qui suit :
class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
<span class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home
urlpatterns = [
path(‘’, home)
]
Il est temps de créer la vue vers laquelle nous sommes dirigés. Passez à books/views.py et ajoutez une définition avec la requête comme paramètre :
class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home</span><span class="hljs-params">(request)</span>:
class="hljs-keyword">return</span> render(request, “index.html”)
Finalement, créez le fichier index.html. Il doit être ajouté au dossier books/templates. Consultez l'image ci-dessous à titre de référence :
Si vous utilisez VSCode, vous pouvez taper !+ENTER dans le fichier. Je vais vous donner un squelette de la page HTML, de sorte que vous soyez en mesure d'ajouter "Hello World" au corps de la page ou quelque chose de similaire. Voici un exemple :
<html>
<body> hello world! body>
html>
Saisissez python manage.py migrate pour appliquer les changements et rendez-vous sur http://127.0.0.1:8000/books. Il se peut que l'on vous demande de faire fonctionner le serveur avec python manage.py runserver une fois de plus. Après cela, le serveur devrait fonctionner sans problème.
Ajouter des objets à partir de la page d'administration
En revenant à la page d'administration, nous pouvons maintenant effectuer toutes les actions CRUD sur le modèle créé. Pour cela passez à books/admin.py, importez le modèle depuis .models, et enregistrez-le :
class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> Book
admin.site.register(Book)
Si vous passez à [http://127.0.0.1:8000/admin](http://127.0.0.1:8000/admin), vous devriez maintenant voir le tableau BOOKS avec des options CRUD. Je vous recommande d'ajouter quelques livres pour les étapes suivantes.  Vous pouvez éventuellement implémenter une fonction __str__ dans la classe du modèle pour obtenir une version plus lisible de l'objet. Par exemple, il pourrait s'agir de quelque chose d'aussi simple que ce qui suit :
class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span><span class="hljs-params">(self)</span>:
class="hljs-keyword">return</span> self.title+” “+self.author
Si vous n'êtes pas familier avec cela, consultez n'importe quel article sur la POO (Programmation Orientée Objet) sur Python. Celui-ci en contient un exemple, surtout si vous voulez en savoir plus sur la méthode __str__.
Les images ci-dessous montrent le portail d'administration avant et après sa mise en œuvre :
Afficher les objets sur la page d'accueil - CRUD Read
Revenez au fichier views.py, où nous avons défini la fonction home. Importez votre modèle et ajoutez-le à la fonction. Cela vous permettra d'y accéder depuis le HTML :
class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> Book
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home</span><span class="hljs-params">(request)</span>:
books = Book.objects.all()
class="hljs-keyword">return</span> render(request, “index.html”, {“books”:books})
Maintenant, dans le fichier index.html vous pouvez accéder directement à tous les champs de votre modèle. Par exemple, j'ai décidé d'afficher une liste complète de tous les articles avec le code suivant :
<body>
…
<ul>
{% for book in books %}
<li> {{book.id}} - {{book.title}} by {{book.author}} li>
{% endfor %}
ul>
body>
Encore une fois, si vous n'êtes pas familier avec ce type de code, vous pouvez consulter en ligne toute documentation fiable sur les bases du HTML, et cliquer ici pour en savoir plus sur les balises {%%}.
Reprenons l'image du début de cet article. Elle nous montre que le client envoie une requête (c'est-à-dire qu'il ouvre le site web) et que l'URL l'adresse à une vue (la fonction accueil), qui répond avec les modèles contenant les informations de la base de données. Cette réponse est renvoyée au client sous la forme d'un modèle (HTML).
Actuellement, il devrait ressembler à l'image ci-dessous :
Ajouter de nouveaux objets - CRUD Create
Tout d'abord, il faut décider de la manière dont nous prévoyons d'obtenir les informations pour le nouvel objet. J'ai choisi de construire un simple formulaire. Cependant, il faudra spécifier le token pour protéger notre serveur contre les interactions malveillantes. Pour en savoir plus sur la sécurité, cliquez [ici](https://docs.djangoproject.com/en/4.2/ref/csrf/).
<body>
…
<form action=”{% url ‘save’ %}” method=”POST”>
{% csrf_token %}
<input type=”text” name=”title”>
<input type “text” name=”author”>
<button type=”submit”>savebutton>
form>
body>
Une fois de plus, nous avons une requête du client (c'est-à-dire lorsque l'utilisateur remplit le formulaire), et nous avons besoin d'une URL l'adressant à une vue. L'action sur le formulaire peut spécifier cette URL, alors créons-la.
Dans books/urls.py nous allons importer save depuis les vues et ajouter un nouveau chemin :
class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home, save
urlpatterns = [
…
path(‘save/’, save, name=”save”),
]
Finalement, nous créons la vue :
class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save</span><span class="hljs-params">(request)</span>:
formsTitle = request.POST.get(“title”)
formsAuthor = request.POST.get(“author”)
Book.objects.create(title=formsTitle, author=formsAuthor)
books = Book.objects.all()
class="hljs-keyword">return</span> render(request, “index.html”, {“books”: books})
Si nous rechargeons la page, nous verrons les entrées et le bouton. Vous pouvez également ajouter quelques objets supplémentaires pour vérifier le fonctionnement. En outre, vous pouvez ouvrir le portail SQL sur le portail de gestion IRIS pour voir comment le formulaire envoie les informations directement au tableau IRIS.
Modification d'objets existants - CRUD Update
Tout comme pour les opérations que nous avons créées précédemment, nous devons construire quelque chose pour envoyer la demande. Par exemple, nous pouvons ajouter un lien à chaque objet qui renvoie vers un formulaire analogue à celui que nous venons de construire.
Commençons par modifier le index.html:
<body>
…
<ul>
{% for book in books %}
<li>
{{book.id}} - {{book.title}} by {{book.author}}
<a href=”{% url ‘edit’ book.id %}”>Edita>
li>
{% endfor %}
ul>
…
body>
Maintenant, dans le fichier urls.py nous devons ajouter la nouvelle URL, avec le paramètre ID :
class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home, save, edit
urlpatterns = [
…
path(‘edit/’, edit, name=”edit”),
]
Ensuite, nous pouvons créer la vue édition dans views.py:
class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">edit</span><span class="hljs-params">(request, id)</span>:
book = Book.objects.get(id=id)
class="hljs-keyword">return</span> render(request, “update.html”, {“book”: book})
La vue édition va envoyer un nouveau modèle à l'utilisateur afin qu'il puisse saisir les informations nécessaires à la mise à jour de l'objet. Le fichier update.html doit être créé dans books/templates. Vous trouverez ci-dessous un exemple de modèle qui répond à nos besoins :
<html>
<body>
<form action=”{% url ‘update’ book.id %}” method=”POST”>
{% csrf_token %}
<input type=”text” name=”title” value=”{{book.title}}”>
<input type=“text” name=”author” value=”{{book.author}}”>
<button type=”submit”>updatebutton>
form>
body>
html>
Puisque nous avons une nouvelle URL, nous devons la créer dans urls.py et spécifier sa vue dans views.py:
class="hljs-comment"># le fichier books/urls.py</span>
<span class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home, save, edit, update
urlpatterns = [
…
path(‘update/’, update, name=”update”),
]
class="hljs-comment"># le fichier books/views.py</span>
<span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> redirect
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update</span><span class="hljs-params">(request, id)</span>:
book = Book.objects.get(id=id)
book.title = request.POST.get(“title”)
book.author = request.POST.get(“author”)
book.save()
class="hljs-keyword">return</span> redirect(home)
À présent, notre retour va se faire vers le modèle renvoyé par la vue d'accueil.
À ce stade, vous pouvez recharger le programme et tout tester, en vérifiant les informations correspondantes sur IRIS.
Suppression d'un objet - CRUD Delete
Nous devons faire en sorte que le client puisse interagir avec le serveur d'une manière qui permette à ce dernier de supprimer des informations. Il existe de nombreuses façons de rendre cela possible. Je vais simplement ajouter un autre lien qui contient une URL, qui adresse cette information à une vue, à la liste d'affichage.
Modifiez le fichier index.html comme indiqué ci-dessous :
<body>
…
<ul>
{% for book in books %}
<li>
{{book.id}} - {{book.title}} by {{book.author}}
<a href=”{% url ‘edit’ book.id %}”>Edita>
<a href = “{% url ‘delete’ book.id %}”>Deletea>
li>
{% endfor %}
ul>
…
body>
J'espère que vous avez appris suffisamment de choses sur ce processus pour deviner qu'à ce stade, nous devons créer un chemin sur urls.py:
class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home, save, edit, update, delete
urlpatterns = [
…
path(‘delete/’, delete, name=”delete”),
]
Pour finir, créez la vue :
class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">delete</span><span class="hljs-params">(request, id)</span>:
book = book.objects.get(id=id)
book.delete()
class="hljs-keyword">return</span> redirect(home)
Rechargez la page. Votre nouveau lien est prêt à être testé :
Conclusion
Dans cet article, nous avons créé la base de presque tous les serveurs. Il devrait vous être facile d'imaginer comment faire évoluer ce que nous avons fait aujourd'hui vers un système d'enregistrement ou de facturation, ou peut-être même un blog, en ajoutant simplement quelques fonctions de sécurité (et, bien sûr, un tout petit peu de CSS).
Cependant, nous pouvons encore approfondir ce que nous avons appris ici pour créer des portails qui peuvent afficher toutes les informations que vous avez déjà sur votre instance IRIS, en faisant évoluer notre serveur vers un portail de gestion pour contrôler le flux de données, l'interaction avec l'utilisateur, l'affichage d'informations sur les journaux et même l'envoi de courriels. En outre, nous pouvons également utiliser Django pour créer un point final, tel qu'une page web facile à utiliser pour interagir avec les API que nous construisons à partir d'IRIS, y compris le traitement, la transformation et la surveillance des données que les plates-formes d'InterSystems fournissent.
Article
Guillaume Rongier · Jan 20, 2023
Intersystems IRIS for Health offre un excellent support pour la norme sectorielle FHIR. Les principales caractéristiques sont :
1. Serveur FHIR
2. Base de données FHIR
3. API REST et ObjectScript pour les opérations CRUD sur les ressources FHIR (patient, questionnaire, vaccins, etc.)
Cet article explique comment utiliser chacune de ces fonctionnalités, et présente un front-end angulaire permettant de créer et d'afficher des ressources FHIR de type Quiz.
## Étape 1 - Déploiement de votre serveur FHIR à l'aide d'InterSystems IRIS for Health
Pour créer votre serveur FHIR, il faut ajouter les instructions suivantes dans le fichier iris.script ( à partir de : https://openexchange.intersystems.com/package/iris-fhir-template)
zn "HSLIB"
set namespace="FHIRSERVER"
Set appKey = "/fhir/r4"
Set strategyClass = "HS.FHIRServer.Storage.Json.InteractionsStrategy"
set metadataPackages = $lb("hl7.fhir.r4.core@4.0.1")
set importdir="/opt/irisapp/src"
//Install a Foundation namespace and change to it
Do ##class(HS.HC.Util.Installer).InstallFoundation(namespace)
zn namespace
// Install elements that are required for a FHIR-enabled namespace
Do ##class(HS.FHIRServer.Installer).InstallNamespace()
// Install an instance of a FHIR Service into the current namespace
Do ##class(HS.FHIRServer.Installer).InstallInstance(appKey, strategyClass, metadataPackages)
// Configure FHIR Service instance to accept unauthenticated requests
set strategy = ##class(HS.FHIRServer.API.InteractionsStrategy).GetStrategyForEndpoint(appKey)
set config = strategy.GetServiceConfigData()
set config.DebugMode = 4
do strategy.SaveServiceConfigData(config)
zw ##class(HS.FHIRServer.Tools.DataLoader).SubmitResourceFiles("/opt/irisapp/fhirdata/", "FHIRServer", appKey)
do $System.OBJ.LoadDir("/opt/irisapp/src","ck",,1)
zn "%SYS"
Do ##class(Security.Users).UnExpireUserPasswords("*")
zn "FHIRSERVER"
zpm "load /opt/irisapp/ -v":1:1
//zpm "install fhir-portal"
halt
En utilisant la classe utilitaire HS.FHIRServer.Installer, vous pouvez créer votre serveur FHIR.
## Étape 2 - Utilisez l'API FHIR REST ou ObjectScript pour lire, mettre à jour, supprimer et trouver des données FHIR
Je préfère utiliser la classe ObjectScript HS.FHIRServer.Service pour faire toutes les opérations CRUD.
### Pour obtenir toutes les données FHIR provenant d'un type de ressource (comme le questionnaire) :
/// Retreive all the records of questionnaire
ClassMethod GetAllQuestionnaire() As %Status
{
set tSC = $$$OK
Set %response.ContentType = ..#CONTENTTYPEJSON
Set %response.Headers("Access-Control-Allow-Origin")="*"
Try {
set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
set request = ##class(HS.FHIRServer.API.Data.Request).%New()
set request.RequestPath = "/Questionnaire/"
set request.RequestMethod = "GET"
do fhirService.DispatchRequest(request, .pResponse)
set json = pResponse.Json
set resp = []
set iter = json.entry.%GetIterator()
while iter.%GetNext(.key, .value) {
do resp.%Push(value.resource)
}
write resp.%ToJSON()
} Catch Err {
set tSC = 1
set message = {}
set message.type= "ERROR"
set message.details = "Error on get all questionnairies"
}
Quit tSC
}
### Pour obtenir un élément de données spécifique du référentiel de données FHIR (comme une occurrence de questionnaire) :
/// Retreive a questionnaire by id
ClassMethod GetQuestionnaire(id As %String) As %Status
{
set tSC = $$$OK
Set %response.ContentType = ..#CONTENTTYPEJSON
Set %response.Headers("Access-Control-Allow-Origin")="*"
Try {
set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
set request = ##class(HS.FHIRServer.API.Data.Request).%New()
set request.RequestPath = "/Questionnaire/"_id
set request.RequestMethod = "GET"
do fhirService.DispatchRequest(request, .pResponse)
write pResponse.Json.%ToJSON()
} Catch Err {
set tSC = 1
set message = {}
set message.type= "ERROR"
set message.details = "Error on get the questionnaire"
}
Quit tSC
}
### Pour créer une nouvelle occurrence de ressource FHIR (comme un nouveau questionnaire) :
/// Create questionnaire
ClassMethod CreateQuestionnaire() As %Status
{
set tSC = $$$OK
Set %response.ContentType = ..#CONTENTTYPEJSON
Set %response.Headers("Access-Control-Allow-Origin")="*"
Try {
set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
set request = ##class(HS.FHIRServer.API.Data.Request).%New()
set request.RequestPath = "/Questionnaire/"
set request.RequestMethod = "POST"
set data = {}.%FromJSON(%request.Content)
set data.resourceType = "Questionnaire"
set request.Json = data
do fhirService.DispatchRequest(request, .response)
write response.Json.%ToJSON()
} Catch Err {
set tSC = 1
set message = {}
set message.type= "ERROR"
set message.details = "Error on create questionnaire"
}
Return tSC
}
### Pour mettre à jour une ressource FHIR (comme un questionnaire) :
/// Update a questionnaire
ClassMethod UpdateQuestionnaire(id As %String) As %Status
{
set tSC = $$$OK
Set %response.ContentType = ..#CONTENTTYPEJSON
Set %response.Headers("Access-Control-Allow-Origin")="*"
Try {
set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
set request = ##class(HS.FHIRServer.API.Data.Request).%New()
set request.RequestPath = "/Questionnaire/"_id
set request.RequestMethod = "PUT"
set data = {}.%FromJSON(%request.Content)
set data.resourceType = "Questionnaire"
set request.Json = data
do fhirService.DispatchRequest(request, .response)
write response.Json.%ToJSON()
}Catch Err {
set tSC = 1
set message = {}
set message.type= "ERROR"
set message.details = "Error on update questionnaire"
}
Return tSC
}
### Pour supprimer un occurrence de ressource FHIR (comme un questionnaire) :
/// Delete a questionnaire by id
ClassMethod DeleteQuestionnaire(id As %String) As %Status
{
set tSC = $$$OK
Set %response.ContentType = ..#CONTENTTYPEJSON
Set %response.Headers("Access-Control-Allow-Origin")="*"
Try {
set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
set request = ##class(HS.FHIRServer.API.Data.Request).%New()
set request.RequestPath = "/Questionnaire/"_id
set request.RequestMethod = "DELETE"
do fhirService.DispatchRequest(request, .pResponse)
} Catch Err {
set tSC = 1
set message = {}
set message.type= "ERROR"
set message.details = "Error on delete the questionnaire"
}
Quit tSC
}
Comme vous pouvez le voir, pour créer, il faut utiliser POST, pour mettre à jour, il faut utiliser PUT, pour supprimer, il faut utiliser DELETE et pour lancer une requête, il faut utiliser le verbe GET.
## Étape 3 - Créez un client en Angular pour utiliser votre application de serveur FHIR.
J'ai créé une application angulaire en utilisant PrimeNG et en installant le paquet npm install --save @types/fhir. Ce paquet a tous les types FHIR mappé à TypeScript.
### Classe de contrôleur en Angular :
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Period, Questionnaire } from 'fhir/r4';
import { ConfirmationService, MessageService, SelectItem } from 'primeng/api';
import { QuestionnaireService } from './questionnaireservice';
const QUESTIONNAIREID = 'questionnaireId';
@Component({
selector: 'app-questionnaire',
templateUrl: './questionnaire.component.html',
providers: [MessageService, ConfirmationService],
styleUrls: ['./questionnaire.component.css'],
encapsulation: ViewEncapsulation.None
})
export class QuestionnaireComponent implements OnInit {
public questionnaire: Questionnaire;
public questionnairies: Questionnaire[];
public selectedQuestionnaire: Questionnaire;
public questionnaireId: string;
public sub: any;
public publicationStatusList: SelectItem[];
constructor(
private questionnaireService: QuestionnaireService,
private router: Router,
private route: ActivatedRoute,
private confirmationService: ConfirmationService,
private messageService: MessageService){
this.publicationStatusList = [
{label: 'Draft', value: 'draft'},
{label: 'Active', value: 'active'},
{label: 'Retired', value: 'retired'},
{label: 'Unknown', value: 'unknown'}
]
}
ngOnInit() {
this.reset();
this.listQuestionnaires();
this.sub = this.route.params.subscribe(params => {
this.questionnaireId = String(+params[QUESTIONNAIREID]);
if (!Number.isNaN(this.questionnaireId)) {
this.loadQuestionnaire(this.questionnaireId);
}
});
}
private loadQuestionnaire(questionnaireId) {
this.questionnaireService.load(questionnaireId).subscribe(response => {
this.questionnaire = response;
this.selectedQuestionnaire = this.questionnaire;
if(!response.effectivePeriod) {
this.questionnaire.effectivePeriod = {};
}
}, error => {
console.log(error);
this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on load questionnaire.' });
});
}
public loadQuestions() {
if(this.questionnaire && this.questionnaire.id) {
this.router.navigate(['/question', this.questionnaire.id]);
} else {
this.messageService.add({ severity: 'warn', summary: 'Warning', detail: 'Choose a questionnaire.' });
}
}
private listQuestionnaires() {
this.questionnaireService.list().subscribe(response => {
this.questionnairies = response;
this.reset();
}, error => {
console.log(error);
this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on load the questionnaries.' });
});
}
public onChangeQuestionnaire() {
if (this.selectedQuestionnaire && !this.selectedQuestionnaire.id) {
this.messageService.add({ severity: 'warn', summary: 'Warning', detail: 'Select a questionnaire.' });
} else {
if(this.selectedQuestionnaire && this.selectedQuestionnaire.id) {
this.loadQuestionnaire(this.selectedQuestionnaire.id);
}
}
}
public reset() {
this.questionnaire = {};
this.questionnaire.effectivePeriod = {};
}
public save() {
if(this.questionnaire.id && this.questionnaire.id != "") {
this.questionnaireService.update(this.questionnaire).subscribe(
(resp) => {
this.messageService.add({
severity: 'success',
summary: 'Success', detail: 'Questionnaire saved.'
});
this.listQuestionnaires()
this.loadQuestionnaire(this.questionnaire.id);
},
error => {
console.log(error);
this.messageService.add({
severity: 'error',
summary: 'Error', detail: 'Error on save the questionnaire.'
});
}
);
} else {
this.questionnaireService.save(this.questionnaire).subscribe(
(resp) => {
this.messageService.add({
severity: 'success',
summary: 'Success', detail: 'Questionnaire saved.'
});
this.listQuestionnaires()
this.loadQuestionnaire(resp.id);
},
error => {
console.log(error);
this.messageService.add({
severity: 'error',
summary: 'Error', detail: 'Error on save the questionnaire.'
});
}
);
}
}
public delete(id: string) {
if (!this.questionnaire || !this.questionnaire.id) {
this.messageService.add({ severity: 'warn', summary: 'Warning', detail: 'Select a questionnaire.' });
} else {
this.confirmationService.confirm({
message: 'Do you confirm?',
accept: () => {
this.questionnaireService.delete(id).subscribe(
() => {
this.messageService.add({ severity: 'success', summary: 'Success', detail: 'Questionnaire deleted.' });
this.listQuestionnaires();
this.reset();
},
error => {
console.log(error);
this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on delete questionnaire.' });
}
);
}
});
}
}
}
Fichier HTML Angular
Name
Title
Date
Status
Publisher
Start Period
End Period
Description
### Classe de Service Angular
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
import { take } from 'rxjs/operators';
import { Questionnaire } from 'fhir/r4';
@Injectable({
providedIn: 'root'
})
export class QuestionnaireService {
private url = environment.host2 + 'questionnaire';
constructor(private http: HttpClient) { }
public save(Questionnaire: Questionnaire): Observable {
return this.http.post(this.url, Questionnaire).pipe(take(1));
}
public update(Questionnaire: Questionnaire): Observable {
return this.http.put(`${this.url}/${Questionnaire.id}`, Questionnaire).pipe(take(1));
}
public load(id: string): Observable {
return this.http.get(`${this.url}/${id}`).pipe(take(1));
}
public delete(id: string): Observable {
return this.http.delete(`${this.url}/${id}`).pipe(take(1));
}
public list(): Observable {
return this.http.get(this.url).pipe(take(1));
}
}
## Step 4 - Application in action
1. Aller à l'application https://openexchange.intersystems.com/package/FHIR-Questionnaires.
2. Clone/git dépose le dépôt dans n'importe quel répertoire local.
$ git clone https://github.com/yurimarx/fhir-questions.git
3. Ouvrir le terminal dans ce répertoire et exécutez :
$ docker-compose up -d
4. Ouvrir l'application web : http://localhost:52773/fhirquestions/index.html
Voici quelques illustrations :
.png)
.png)
Article
Guillaume Rongier · Sept 12, 2022
InterSystems participe aux Rencontres autour de l'appel à projet EDS, organisés par PariSanté Campus et le HDH (Health Data Hub) - Jeudi 12 septembre 2022

Nous partageons ici nos notes publiques pour faire avancer le débat et invitons toutes les personnes mentionnées à les annoter, commenter ou amender.
Grand merci à @Adeline.Icard pour ces lignes !
En vrac, lors des tables rondes :
**Table ronde 1: Embarquer les professionnels de santé et usagers autour des EDS**
Arthur DAUPHIN, Chargé de mission Numérique en santé chez France Assos Santé
Il est nécessaire de la confiance entre les usagers (patients) et les professionnels de santé pour bien comprendre la finalité et l’intérêt général d’un partage des données de santé.
David MALLET, Directeur de la recherche clinique et de l'innovation au CHU Rouen
Comment obtenir le consentement du patient pour stocker leurs données dans l’EDS?
Informer les patients et expliquer comment sont stockées les données, dans le respect des normes éthiques, est fondamental.
Philippe BERTHEAU, Professeur en Anatomie et Cytologie Pathologiques à l'AP-HP et l'Université Paris Cité
Comment engager les professionnels de santé ?
L'organisation de data challenge est une possibilité pour engager les professionnels de santé et usagers. Ex, data challenge international sur une pathologie (ex col de l’utérus) : 3000 patientes ont été incluses dans ce data challenge
Un EDS représente un investissement consequent pour les professionnels de santé, car il est nécessaire d'extraire les données, de les saisir, de les nettoyer ….
Éric Vibert, Professeur de Chirurgie Digestive au Centre Hépato-Biliaire / Directeur de la Chaire Innovation BOPA à l'AP-HP
Pour embarquer les professionnels de santé, il faut mettre à leur disposition des outils modernes et performants ! Il n’est plus possible de continuer à travailler avec des tableaux Excel !
Hélène GUIMIOT-BREAUD, Cheffe du service de la santé à la CNIL
**Table ronde 2 : Financement pérenne des EDS**
Claude GISSOT, Directeur de projet AMDAC à la DREES
Un financement pérenne pour accompagner et faire évoluer les EDS.
Pour pouvoir également recruter, former et accompagner des équipes multi origines (IT, clinique, datascientistes ….) dans la durée.
Carole DUFOUIL, Directrice de recherches à l'INSERM
Les EDS n’est pas que pour la recherche mais doit également servir au pilotage des établissements de santé et le développement de bonnes pratiques
Florence FAVREL-FEUILLADE, Directrice Générale du CHRU de Brest, Présidente de la commission Recherche et Innovation de la conférence des DG de CHU, Personnalité Qualifiée de France 2030
Cet AAP était attendu depuis très longtemps. C’est une bonne chose. Il pose un cadre technique national. C’est un début ….
Coût EDS moyen 800 000 a 1M de frais de exploitation annuel
Il faudra aussi des AAP et financements pour développer les usages à partir des EDS
Un financement spécifique devra suivre dans la logique pour financer la recherche clinique
=> financement complémentaire indispensable pour des usages pérenne des EDS
Alexandre VAINCHTOCK, Cofondateur de la société HEVA
Comment valoriser la donnée ?
Il est difficile d’avoir des grilles tarifaires de prestations communes …. Les établissements manquent de ressources administratives et juridiques.
Ce travail reste à faire pour pérenniser le développement des usages de la data dans les EDS
**Table ronde 3 : Identifier les données pertinentes à ingérer dans un EDS**
les EDS sont d'excellents outils pour accroître la réactivité du système de santé.
Jean-Yves BLAY, Président d'Unicancer
Quelles données ? Toutes, celles du dpi, de l’imagerie …. Extraction massive
Objectif : améliorer les soins et le suivi des patients notamment en oncologie
Exemples :
Quelles sont les performances et les nouvelles stratégies pour le suivi des patients ?
Comment mieux prédire la vie générale du patient lorsqu’il est guéri ?
**Enjeu clé de l’EDS : permettre la recherche sur ce qu’on ne connaît pas encore !**
Nous avons besoin de bcp de jeunes médecins qui sont sensibles à ces nouveaux enjeux et thématiques IT autour de la donnée.
Il y a tant à faire encore !
Raphaele RENARD PENNA, Radiologue et Professeur des Universités à la Faculté Sorbonne Université, Hôpital Pitié Salpétrière
Il est important d’avoir un accès facilité aux données, de pouvoir les partager et les comparer …. Des données hétérogènes.
Et créer des plateformes collaboratives pour développer des projets de recherches.
Pour faire avancer un projet de recherche, nous manquons de moyen informatique pour saisir, extraire, nettoyer les données ….
Bernard HAMELIN, Managing Partner chez Euresis Partners
Les usages de la données de santé explosent depuis les dernières années.
Florence ALLOUCHE, Presidente de MYRPHARM ADVISORS, Professeur Innovation & Entrepreneuriat, Faculté de Pharmacie de Paris – University Paris Cité, Membre de l’Academie Nationale de Pharmacie
**Il faut déverrouiller les idéologies pour avancer ! **
Développer les partenariats entre organismes publics et industriels.
Il est indispensable de faciliter l’accès aux données pour permettre aux start-ups d’innover et se développer en France, en Europe mais aussi à l’international !!!! **Let’s go please !**
Jean-François THEBAUT, Vice-Président de la Fédération Française des Diabétiques
Ne perdons pas de vue que la finalité des EDS est la qualité des soins aux patients. Ils doivent permettre d'accroître la sécurité des patients, la surveillance des signaux faibles, la prise en compte des inégalités au sein de la population, la prise en compte de la multiplicité des maladies chronique et des patients qui ne passent pas dans les hôpitaux …
**L' EDS doit avoir un impact sur la vie réel du patient !**
Conclusion
Romain BONENFANT, Chef du service de l’Industrie à la DGE
Un cadre se structure en France et en Europe pour faciliter le développement du numérique en santé. Il doit encourager la collaboration entre prive public, prestataires de soins, chercheurs, industriels/start-ups.
Sandrine BILLET, Sous directrice Pilotage de la performance des acteurs de l’offre de soins à la DGOS
**« Je félicite l’enthousiasme des différents acteurs face à ce 1er AAP. Les enjeux sont forts pour la suite, en matière de financement pérenne, développement des usages et compétitivité de la France sur la scène internationale. Je vous remercie tous pour votre engagement »**
[Pour en savoir plus.](https://www.health-data-hub.fr/rencontres-aap-eds)

Wow, les sujets abordés sont très captivants! Je hâte de lire vos commentaires!
Article
Robert Bira · Oct 26, 2022
Ces dernières années, l'émergence de la norme FHIR (Fast Healthcare Interoperability Resources) a suscité un enthousiasme croissant parmi les experts en informatique médicale du monde entier. Cette tendance n'est pas juste une autre itération des normes et de la technologie. Elle a le potentiel de transformer et révolutionner l'ensemble du secteur. Il est donc nécessaire de la rendre accessible au plus grand nombre, pour qu’elle ne se cantonne pas au domaine technique, mais soit adoptée par les décideurs et leaders d'opinion. Si SantExpo 2022 a déjà dévoilé de grands progrès à cet égard, nous devons aller encore plus loin.
FHIR face au défi d’interopérabilité du système de santé actuel
Beaucoup d'entre nous se sont habitués à certaines pratiques en matière de données de santé et d’interopérabilité, en raison d’années de travail avec les précédentes générations de normes et de technologies - notamment HL7 V2. Mais, que ce soit au niveau des établissements de soins ou des éditeurs de logiciels santé, il est communément admis que les intégrations entre différents systèmes d’information génèrent un bon nombre de défis.
Ralentissement des déploiements dans la mesure où les éditeurs (parfois concurrents) sont souvent obligés d’échanger des informations et collaborer pour concevoir, implémenter et tester ces intégrations.
Dépendance vis à vis des éditeurs, qui peut conduire à des « guerres de territoire » dans lesquelles les utilisateurs finaux n’ont que peu d’influence et qui alimente l’activité lucrative des connecteurs.
Création d’une architecture n-to-n complexe à maintenir et généralement peu efficiente relative au volume de données qui transitent.
Au-delà des interfaces standards (eg identité/mouvement), certaines intégrations sont très customisées et peuvent entrainer des risques, notamment pour la sécurité des patients voire des ruptures de prise en charge, et de ce fait ne dépassent souvent pas l’état d’ébauche. Au-delà des conséquences financières évidentes, ces points de tension impactent en premier lieu les utilisateurs finaux (médecins, infirmières, etc.) en complexifiant les processus de prise en charge des patients. De plus, les start-ups ou les nouveaux entrants sur le marché se trouvent confrontés à ces barrières difficilement franchissables. Les challenges de l'interopérabilité ont donc une forte responsabilité dans les difficultés rencontrées en matière de mise en œuvre, adoption et innovation numérique en santé.
FHIR ou la promesse d’un meilleur partage des données
Si FHIR a le potentiel de réduire considérablement les points sensibles actuels et d'introduire un nouveau paradigme en matière d'échange de données de santé, il y a toutefois une condition importante. Le FHIR doit être largement adopté ; et bien que ce soit le cas dans certaines parties du monde, la France en est encore aux premiers jours de son adaptation. En combinant les normes modernes et la technologie API contemporaine, le FHIR permettra un interfaçage beaucoup plus rapide, plus standard et plus transparent entre les différents systèmes d’information. On peut supposer que dans les années à venir, tous les logiciels santé adopteront la norme FHIR. Ainsi, les principaux DPI (Dossier Patient Informatisé) disposeront de leurs propres référentiels FHIR, les nouvelles applications cliniques destinées à traiter des secteurs niches ou des cas d'usage spécifiques seront natives FHIR, et la plupart des entrepôts de données de santé seront compatibles FHIR. Dans ce contexte, les intégrations seront plus rapides, transparentes et moins dépendantes des éditeurs car tous les systèmes parleront le même langage. L'efficacité en termes de flux de données sera également grandement améliorée, ce qui se traduira par de meilleures performances et une meilleure qualité des données. Les éditeurs se concentreront ainsi davantage sur le développement de logiciels innovants et les experts FHIR sur la gestion de l'interopérabilité.
Un standard qui se décline jusqu’au stockage des données
La conséquence la plus disruptive de l'adoption de FHIR pourrait bien être un nouveau paradigme concernant le stockage des données de santé. En effet, motivée par les besoins croissants d'analyse en temps réel, l'ambition de FHIR va au-delà de l'interopérabilité. Un entrepôt FHIR stocke les données cliniques, ce qui ouvre beaucoup de nouvelles possibilités d'applications. Il est vrai que l'utilisation de FHIR comme modèle de données est une décision en soi et dispose de ses avantages et inconvénients. Elle nécessite aussi les outils appropriés pour exploiter cet entrepôt au format documentaire. Le stockage FHIR des données cliniques répondra au besoin croissant de partage des données puisqu'il marie les concepts de base de données et d'échange de données. Nous constatons aujourd'hui la multiplication d’entrepôts de données de santé: à l'hôpital, entre hôpitaux (GHT, régions ou consortiums), dans les entreprises comme l'industrie pharmaceutique (recherche de nouveaux médicaments, données de vie réelle) ou encore les assurances, les institutions, les agences gouvernementales, et bien d'autres encore. Pour des raisons de recherche scientifique il faut pouvoir permettre un partage aisé des données de santé entre ces référentiels. Et c'est également une évidence en matière d’Intelligence Artificielle et de Machine Learning, où les chercheurs ont régulièrement besoin de nouvelles données pour améliorer leurs modèles.
Une évolution qui doit s’inscrire dans la durée
Une telle transition se fera sur plusieurs années et de nombreux des défis devront être relever en cours de route.
Il faudra du temps et de l'énergie pour interpréter et adopter FHIR. Heureusement, il existe une documentation et des prestataires de services de plus en plus expérimentés.
Globalement, nous continuerons à être confronté à des limitations qui nécessiteront un retour à la communauté HL7 FHIR pour des développements futurs, comme par exemple sur le stockage des données.
La coexistence des anciens et des nouveaux formats peut rendre les choses complexes - heureusement, il existe des outils et des technologies pour nous accompagner.
Bien que personne ne sache à quelle vitesse cette transition s’opérera, il est intéressant de noter que plus de 80 % des hôpitaux américains ont déjà adopté FHIR en 2019. Nous observons également un nombre croissant de start-ups ou de nouveaux logiciels qui choisissent de se lancer en utilisant nativement FHIR dans leurs applications. Alors en conclusion, nous devons continuer, éditeurs, chercheurs, utilisateurs finaux, à faire pression pour que les décideurs adoptent une position plus importante et plus audacieuse sur le sujet et fassent massivement le choix de ce langage commun porteur d’innovation pour le secteur de la santé.
Allez à la publication initiale écrit par Nicolas Eiferman, Directeur Général France d’InterSystems
Article
Iryna Mykhailova · Jan 13, 2023
Comme vous vous en souvenez peut-être du Global Summit 2022 ou du webinaire de lancement 2022.2, nous lançons une nouvelle fonctionnalité passionnante à inclure dans vos solutions d'analyse sur InterSystems IRIS. Le stockage en colonnes introduit une autre façon de stocker vos données de table SQL qui offre une accélération d'ordre de grandeur pour les requêtes analytiques. Publié pour la première fois en tant que fonctionnalité expérimentale en 2022.2, le dernier Developer Preview 2022.3 comprend un tas de mises à jour qui, selon nous, valaient la peine d'être publiées ici.
Un récapitulatif rapide
Si vous n'êtes pas familier avec le stockage en colonnes, veuillez regarder cette brève vidéo d'introduction ou la session GS2022 sur le sujet. En bref, nous encodons les données de la table en morceaux de 64k valeurs par colonne en utilisant un nouveau type de données $vector. $vector est un type de données interne uniquement (pour l'instant) qui exploite des schémas de codage adaptatifs pour permettre un stockage efficace des données clairsemées et denses. L'encodage est également optimisé pour un tas d'opérations $vector dédiées, telles que le calcul d'agrégats, de regroupements et de filtres de blocs entiers de valeurs de 64k à la fois, en tirant parti des instructions SIMD lorsque cela est possible.
Au moment de la requête SQL, nous tirons parti de ces opérations en créant un plan de requête qui fonctionne également sur ces morceaux, ce qui, comme vous pouvez l'imaginer, entraîne une réduction massive de la quantité d'E/S et du nombre d'instructions ObjectScript pour exécuter votre requête, par rapport au classique traitement ligne par ligne. Bien sûr, les E/S individuelles sont plus grandes et les opérations $vector sont un peu plus lourdes que les homologues à valeur unique du monde orienté ligne, mais les gains sont énormes. Nous utilisons le terme plans de requête vectorisés pour les stratégies d'exécution qui traitent des données $vector, en poussant des morceaux entiers à travers une chaîne d'opérations individuelles rapides.
Juste plus rapide
Surtout, les choses se sont accélérées. Nous avons élargi la compréhension de l'optimiseur des index en colonnes et vous verrez désormais plus de requêtes utiliser des index en colonnes, même si certains des champs demandés ne sont pas stockés dans un index en colonnes ou une carte de données. En outre, vous verrez qu'il combine des index en colonnes et des index bitmap dans un certain nombre de cas, ce qui est idéal si vous commencez par ajouter des index en colonnes à un schéma existant.
Le nouveau kit comprend également un ensemble de modifications dans la pile qui améliorent les performances, allant des optimisations à ces opérations $vector de bas niveau en passant par certaines améliorations du traitement des requêtes et un ensemble plus large de plans de requête vectorisés qui peuvent être parallélisés. Certaines méthodes de chargement des données, telles que les instructions INSERT .. SELECT, utiliseront désormais également un modèle tamponné que nous utilisions déjà pour créer des index et permettent désormais une création très performante de tables entières.
JOIN vectorisés
La fonctionnalité la plus intéressante que nous ayons ajoutée dans cette version est la prise en charge de JOINing de données en colonnes de manière vectorisée. En 2022.2, lorsque vous vouliez combiner les données de deux tables dans une requête, nous recourions toujours à une stratégie JOIN ligne par ligne robuste qui fonctionne aussi bien sur les données en colonnes que sur les données organisées en lignes. Désormais, lorsque les deux extrémités du JOIN sont stockées au format colonne, nous utilisons une nouvelle API du noyau pour les JOINDRE en mémoire, en conservant leur format $vector. Il s'agit d'une autre étape importante vers des plans de requête entièrement vectorisés, même pour les requêtes les plus complexes.
Voici un exemple de requête qui tire parti de la nouvelle fonction, en effectuant une auto-JOINTURE de l'ensemble de données New York Taxi que nous avons utilisé dans les démonstrations précédentes :
SELECT
COUNT(*),
MAX(r1.total_amount - r2.total_amount)
FROM
NYTaxi.Rides r1,
NYTaxi.Rides r2
WHERE
r1.DOLocationID = r2.PULocationID
AND r1.tpep_dropoff_datetime = r2.tpep_pickup_datetime
AND r2.DOLocationID = r1.PULocationID
AND r1.passenger_count > 2
AND r2.passenger_count > 2
Cette requête recherche des paires de voyages avec plus de 2 passagers, où le deuxième voyage a commencé là où le premier s'est terminé, exactement à la même heure et où le deuxième voyage a ramené un à l'endroit où le premier a commencé. Pas une analyse super utile, mais je n'avais qu'une seule vraie table dans ce schéma et la clé composite JOIN rendait cela un peu moins trivial. Dans le plan de requête de cette instruction, vous verrez des extraits tels que Apply vector operation %VHASH (pour construire la clé composite JOIN) et Read vector-join temp-file A, qui indiquent que notre nouveau menuisier vectorisé est en train de travailler ! Cela peut sembler être une petite pépite triviale dans un plan de requête assez long, mais cela implique beaucoup d'ingénierie intelligente sur les composants internes, et il existe un certain nombre de fournisseurs de bases de données en colonnes de haut niveau qui n'autorisent tout simplement aucun de cela et mettez de sévères contraintes sur la mise en page de votre schéma, alors s'il vous plaît REJOIGNEZ-NOUS pour nous JOINDRE à cela ! :-)
Lorsque le plan de requête continue à lire ce fichier temporaire, vous remarquerez peut-être qu'il y a encore du traitement ligne par ligne dans le travail post-jointure, ce qui nous amène à...
Et après?
Columnar Storage est toujours marqué comme "expérimental" en 2022.3, mais nous nous rapprochons de la préparation de la production et de cette vectorisation complète de bout en bout pour les requêtes multi-tables. Cela inclut le travail de post-jointure mentionné ci-dessus, une prise en charge plus large dans l'optimiseur de requête, un chargement encore plus rapide des tables en colonnes et d'autres améliorations de la jointure telles que la prise en charge de la mémoire partagée. En bref : c'est le moment idéal pour essayer tout cela pour la première fois avec l'ensemble de données New York Taxi dataset (maintenant sur IPM ou avec docker scripts)
en utilisant l'édition communautaire 2022.3, il vous suffit donc d'appuyer sur "Exécuter" au moment de la sortie de 2023.1 !
Si vous êtes intéressé par des conseils plus personnalisés sur la façon d'exploiter le stockage en colonnes avec vos propres données et requêtes, veuillez me contacter ou contacter directement votre équipe de compte, et nous nous rencontrerons peut-être au Global Summit 2023 ;-).
Article
Robert Cemper · Juil 1, 2022
Je m'intéresse particulièrement à l'utilisation des Globales avec Embedded Python.Alors, j'ai commencé à consulter la documentation officielle.
#1 Introduction to GlobalsUne tentative de description générique de ce qu'est une Globale. Pointant ensuite vers:
#2 A Closer Look at ObjectScriptMais où puis-je trouver Embedded Python ?Plus bas, se trouve:
#3 Embedded Python
3.1 Embedded Python Overview3.1.1 Work with GlobalsIdéal si vous n'avez jamais vu une Globale.Sinon ce n'est qu'un exemple primitif choquant3.2 Using Embedded PythonDernier espoir: >>> Mais, absolument RIEN de visible.
Une déception! Même IRIS Native API for Python est plus détaillé.Pour être clair sur ce que j'attends :
SET, GET, KILL pour un node de Global
Native API: Fundamental Node Operations et
Navigation avec $DATA(), $ORDER(), $QUERY()
Native API: Iteration with nextSubscript() and isDefined()Alors, Il me faut d' enquêter, faire du reverse engineeringet expérimenter moi-même.
Voilà ce que j'ai trouvé:
Tous les exemples sont présentés dans Python Shell comme fourni de IRIS for Windows (x86-64) 2022.1 (Build 209U)faisant usage intensif de la fonction implicite print().
La Globale
Quoi que vous envisagiez de faire, vous devez commencer par la classe iris.gref pour créer un objet de référence pour la Globale.Le nom de la Globale est transmis directement (%String) ou par une variable similaire à l'indirection en COS/ISOS.Le caret initial (^) n'est pas nécessaire, parce qu' il est clair que nous ne traitons que des Globales !
>>> globalname='rcc'
>>> nglob=iris.gref(globalname)
>>> glob=iris.gref('rcc')
>>> cglob=iris.gref('^rcc')
Ce sont 3 references de la même Globale.
C'est seulement une référence, il n'y aucune indication sur l'existance de la globale.
Interactive doc: print(glob.__doc__)InterSystems IRIS global reference object.Use the iris.gref() method to obtain a reference to a global
SUBSCRIPTS
Tous Subscripts des Globales sont passés par Py_list [sub1,sub2]. Pas de grande difference par rapport à COS/ISOSSeul la racine, sans aucun Subscript a besoin d'un traitement spécial.Vous devez utiliser cette construction spéciale [None] pour indiquer que vous faites référence à la racine.
SET
Pour définir une Globale, nous pouvons le faire "directement" comme nous le ferions dans COS/ISOS.
>>> glob[1,1]=11
ou utiliser la méthode gref.set()
>>> glob.set([1,3],13)
Interactive doc: print(glob.set.__doc__)Given the keys of a global, sets the value stored at that key of the global. Example: g.set([i,j], 10) sets the value of the node at key i,j of global g to 10
Pour accéder au contenu d'un noeud, nous pouvons le faire "directement" comme nous le ferions dans COS/ISOS.
>>> glob[1,3]
13
ou utiliser la méthode gref.get()
>>> glob.get([1,1])
11
Interactive doc: print(glob.get.__doc__)Given the keys of a global, returns the value stored at that node of the global.Example: x = g.get([i,j]) sets x to the value stored at key i,j of global g.
Attention: Ce n' est pas $GET() comme vous le connaissez en COS/ISOS
>>> glob.get([1,99])
Traceback (most recent call last):
File "<input>", line 1, in <module>
KeyError: 'Global Undefined'
>>>
Mais en l'utilisant "directement", il agit comme $GET() en COS/ISOS
>>> x=glob[1,99]
>>> print(x)
None
>>>
Ce None signale ce que SQL exprime comme NULL.Il réapparaîtra plus tard.
KILL
Il -y-a seulement la méthod gref.kill() pour arriver au résultat escompté.
>>> glob.kill([1,3])
>>> y=glob[1,3]
>>> print(y)
None
>>>
Interactive doc: print(glob.kill.__doc__)Given the keys of a global, kills that node of the global and its subtree.Example: g.kill([i,j]) kills the node stored at key i,j of global g and any descendants.
$DATA()
La méthode est gref.data()Interactive doc: print(glob.data.__doc__)Given the keys of a global, returns the state of that.Example: x = g.data([i,j]) sets x to 0,1,10,11 0-if undefined, 1-defined, 10-undefined but has descendants, 11-has value and descendants
Cela fonctionne comme prévu.
>>> glob.data()
10
>>> glob.data([None])
10
>>> glob[None]=9
>>> glob.data([None])
11
>>> glob.data([1,1])
1
>>> glob.data([1,3])
0
>>>
$ORDER()
Pour cet exemple, j'ai ajouté quelques noeuds à la globale ^rcc:
>zw ^rcc
^rcc=9
^rcc(1,1)=11
^rcc(1,2)=12
^rcc(2,3,4)=234
^rcc(2,3,5)=235
^rcc(2,4,4)=244
^rcc(7)=7
La méthode est gref.order()Interactive doc: print(glob.order.__doc__)Given the keys of a global, returns the next key of the global.Example: j = g.order([i,j]) sets j to the next second-level key of global g.Donc, nous voyons:
>>> print(glob.order([]))
1
>>> print(glob.order([1]))
2
>>> print(glob.order([2]))
7
>>> print(glob.order([7]))
None
>>> print(glob.order([1,'']))
1
>>> print(glob.order([1,1]))
2
>>> print(glob.order([2,3,]))
4
>>> print(glob.order([2,3,""]))
4
>>> print(glob.order([2,3,4]))
5
>>> print(glob.order([2,4,4]))
None
>>>
Ici, une référence Subscript manquante un %String vide sont équivalents.
$QUERY()
La méthode associée est gref.query()Interactive doc: print(glob.query.__doc__)Traverses a global starting at the specified key, returning each key and value as a tuple.Example: for (key, value) in g.query([i,j]) traverses g from key i,j, returning each key and value in turn
Le comportement de cette méthode est différent de COS/ISOS
ça retourne TOUS les noeuds apres le noeud de référence
ça comprend les valeurs stockées
ça retourne aussi les noeuds virtuelles SANS valeurs et c'est indiqué par None. Notre exemple ressemble à ceci (enveloppé pour plus de lisibilité):
>>> print(list(glob.query()))
[(['1'], None), (['1', '1'], 11), (['1', '2'], 12), (['2'], None),
(['2', '3'], None), (['2', '3', '4'], 234), (['2', '3', '5'], 235),
(['2', '4'], None), (['2', '4', '4'], 244), (['7'], 7)]
>>>
ou plus lisible :
>>> for (key, value) in glob.query():
... print(key,''.ljust(20-len(str(list(key))),'>'),value)
...
['1'] >>>>>>>>>>>>>>> None
['1', '1'] >>>>>>>>>> 11
['1', '2'] >>>>>>>>>> 12
['2'] >>>>>>>>>>>>>>> None
['2', '3'] >>>>>>>>>> None
['2', '3', '4'] >>>>> 234
['2', '3', '5'] >>>>> 235
['2', '4'] >>>>>>>>>> None
['2', '4', '4'] >>>>> 244
['7'] >>>>>>>>>>>>>>> 7
>>>
Ce n'est certainement pas ZWRITE !
Une autre option consiste à obtenir les Subscripts uniquement à l'aide de gref.keys()Interactive doc: print(glob.keys.__doc__)Traverses a global starting at the specified key, returning each key in the global.Example: for key in g.keys([i, j]) traverses g from key i,j, returning each key in turn. >>>
>>> list(glob.keys())
[['1'], ['1', '1'], ['1', '2'], ['2'], ['2', '3'], ['2', '3', '4'], see it here
['2', '3', '5'], ['2', '4'], ['2', '4', '4'], ['7']]
>>>
Et ensuite, j'ai trouvé gref.orderiter() avec:Interactive doc: print(glob.orderiter.__doc__)Traverses a global starting at the specified key, returning the next key and value as a tuple.Example: for (key, value) in g.orderiter([i,j]) traverses g from key i,j, returning the next key and value.
Cela agit comme $ORDER(), récupérant également les valeurs etfournis le sous-Node suivant avec sa valeur comme le $QUERY()voici:
>>> list(glob.orderiter([]))
[(['1'], None), (['1', '1'], 11)]
>>> list(glob.orderiter([1]))
[(['2'], None), (['2', '3'], None), (['2', '3', '4'], 234)]
>>> list(glob.orderiter([2]))
[(['7'], 7)]
>>>
Finallement, il-y-a une méthode gref.getAsBytes() Interactive doc: print(glob.getAsBytes.__doc__)Given the keys of a global, returns a string stored at that node of the global, as bytes.Example: x = g.getAsBytes([i,j]) sets x to the value stored at key i,j of global g, as bytes.
Il échoue pour les valeurs numériques. Mais travailes pour %String:
>>> glob[5]="robert"
>>> glob.get([5])
'robert'
>>> glob.getAsBytes([5])
b'robert'
Et si j'exécute en COS/ISOS: set ^rcc(9)=$lB(99,"robert") Je peux avoir le résultat suivant:
>>> glob[9]
'\x03\x04c\x08\x01robert'
>>> glob.getAsBytes([9])
b'\x03\x04c\x08\x01robert'
>>>
Comment ai-je détecté toutes ces méthodes:
>>> for meth in glob.__dir__():
... meth
...
'__len__'
'__getitem__'
'__setitem__'
'__delitem__'
'__new__'
'data'
'get'
'set'
'kill'
'getAsBytes'
'order'
'query'
'orderiter'
'keys'
'__doc__'
'__repr__'
'__hash__'
'__str__'
'__getattribute__'
'__setattr__'
'__delattr__'
'__lt__'
'__le__'
'__eq__'
'__ne__'
'__gt__'
'__ge__'
'__init__'
'__reduce_ex__'
'__reduce__'
'__subclasshook__'
'__init_subclass__'
'__format__'
'__sizeof__'
'__dir__'
'__class__'
>>>
J'espère que cela vous facilitera la vie si vous avez besoin d'un accès direct aux Globales dans Embedded PythonMon apprentissage personnel : Il y a surtout une documentation . . . . quelque part.Il suffit de creuser et d'explorer.
Video Demo
Merci d'avance pour votre vote au Concours d'articles techniques d'InterSystems : Édition Python Merci pour cet article @Robert.Cemper1003
Ce sera j'en suis sûr très utile! Merci d'avoir corrigé mon français rouillé Merci beaucoup!Alors, on doit être prudent!