#ObjectScript

0 Abonnés · 105 Publications

InterSystems ObjectScript est un langage de script permettant d'opérer avec des données en utilisant n'importe quel modèle de données de la plateforme de données InterSystems (objets, relationnel, clé-valeur, document, globales) et de développer une logique métier pour les applications côté serveur sur la plateforme de données InterSystems.

Documentation.

Article Guillaume Rongier · Oct 6, 2024 21m read

Cela fait maintenant plus de 2 ans que j'utilise quotidiennement Embedded Python. Il est peut-être temps de partager un retour d'expérience sur ce parcours.

Pourquoi écrire ce commentaire de retour d'expérience? Parce que, je suppose, je suis comme la plupart de mes collègues ici, un développeur ObjectScript, et je pense que la communauté bénéficierait de ce retour d'expérience et pourrait mieux comprendre les avantages et les inconvénients du choix de Embedded Python pour développer quelque chose dans IRIS. Et aussi éviter certains pièges.

image

Introduction

Je suis développeur depuis 2010, et j'ai travaillé avec ObjectScript depuis 2013.

Donc, c'est à peu près 10 ans d'expérience avec ObjectScript.

Depuis 2021, et la sortie de Embedded Python dans IRIS, je me suis lancé un défi :

  • Apprendre Python
  • Faire autant que possible tout ce qui est en Python.

Quand j'ai commencé ce parcours, je n'avais aucune idée de ce qu'était Python. J'ai donc commencé par les bases, et je continue d'apprendre chaque jour.

Débuter avec Python

L'avantage de Python est sa facilité d'apprentissage. C'est encore plus facile quand on connaît déjà ObjectScript.

Pourquoi ? Ils ont beaucoup de choses en commun.

ObjectScriptPython
Non typéNon typé
Langage de scriptLangage de script
Orienté objetOrienté objet
InterprétéInterprété
Intégration simple du CIntégration simple du C

Donc, si vous connaissez ObjectScript, vous en savez déjà beaucoup sur Python.

Mais il y a quelques différences, et certaines d'entre elles ne sont pas faciles à comprendre.

Python n'est pas ObjectScript

Mais il y a quelques différences, et certaines d'entre elles ne sont pas faciles à comprendre.

Pour moi il y a principalement 3 différences :

  • Pep8
  • Modules
  • Dunders

Pep8

Mais qu'est-ce que Pep8 ?

Il s'agit d'un ensemble de règles pour écrire du code Python.

pep8.org

Quelques-unes d'entre elles sont :

  • convention de nommage
  • noms de variables
    • snake_case
  • noms de classes
    • CamelCase
  • indentation
  • longueur de ligne
  • etc.

Pourquoi est-ce important ?

Parce que c'est la façon d'écrire du code Python. Et si vous ne suivez pas ces règles, vous aurez du mal à lire le code écrit par d'autres personnes, et elles auront du mal à lire le vôtre.

En tant que développeurs ObjectScript, nous avons aussi quelques règles à suivre, mais elles ne sont pas aussi strictes que Pep8.

J'ai appris Pep8 à la dure.

Je voudrais vous raconter petite histoire, je suis ingénieur commercial chez InterSystems et je fais beaucoup de démonstrations. Et un jour, que je faisais une démo de Embedded Python à un client, et ce client était un développeur Python, la conversation a tourné court lorsqu'il a vu mon code. Il m'a dit que mon code n'était pas du tout Python (il avait raison), je codais en Python comme je codais en ObjectScript. Et à cause de cela, il m'a dit qu'il n'était plus intéressé par Embedded Python. J'ai été choqué, et j'ai décidé d'apprendre Python de la bonne manière.

Donc, si vous voulez apprendre Python, apprenez d'abord Pep8.

Modules

Les modules sont quelque chose que nous n'avons pas en ObjectScript.

Habituellement, dans les langages orientés objet, vous avez des classes et des paquetages. En Python, vous avez des classes, des paquetages et des modules.

Qu'est-ce qu'un module ?

C'est un fichier avec une extension .py. Et c'est la façon d'organiser votre code.

Vous n'avez pas compris? Moi non plus au début. Prenons un exemple.

Habituellement, quand on veut créer une classe en ObjectScript, on crée un fichier .cls, et on y met sa classe. Et si vous voulez créer une autre classe, vous créez un autre fichier .cls. Et si vous voulez créer un paquetage, vous créez un dossier et vous y placez vos fichiers .cls.

En Python, c'est la même chose, mais Python apporte la possibilité d'avoir plusieurs classes dans un seul fichier. Ce fichier s'appelle un module. Pour information, c'est Pythonic quand il y a plusieurs classes dans un seul fichier.

Prévoyez donc comment vous allez organiser votre code, et comment vous allez nommer vos modules pour ne pas vous retrouver comme moi avec un tas de modules portant le même nom que vos classes.

Un mauvais exemple :

MyClass.py

class MyClass:
    def __init__(self):
        pass

    def my_method(self):
        pass

Pour instancier cette classe, vous allez faire :

import MyClass.MyClass # weird right ?

my_class = MyClass()

Bizarre, hein ?

Dunders

Les dunders sont des méthodes spéciales en Python. Elles sont appelées dunder parce qu'elles commencent et se terminent par un double soulignement.

Ce sont en quelque sorte nos méthodes % en ObjectScript.

Elles sont utilisées pour ce qui suit :

  • constructeur
  • surcharge d'opérateur
  • représentation d'objet
  • etc.

Exemple :

class MyClass:
    def __init__(self):
        pass

    def __repr__(self):
        return "MyClass"

    def __add__(self, other):
        return self + other

Ici, nous avons 3 méthodes de dunder :

  • __init__ : constructeur
  • __repr__ : représentation d'objet
  • __add__ : surcharge d'opérateur

Les méthodes Dunders sont partout en Python. C'est une partie importante de la langue, mais ne vous inquiétez pas, vous les apprendrez rapidement.

Conclusion

Python n'est pas ObjectScript, et vous devrez l'apprendre. Mais ce n'est pas si difficile, et vous l'apprendrez rapidement. Gardez simplement à l'esprit qu'il vous faudra apprendre Pep8, et comment organiser votre code avec des modules et des méthodes dunder.

De bons sites pour apprendre Python :


Embedded Python

Maintenant que vous en savez un peu plus sur Python, parlons de Embedded Python.

Qu'est-ce que Embedded Python ?

Embedded Python est un moyen d'exécuter du code Python dans IRIS. C'est une nouvelle fonctionnalité d' IRIS 2021.2+. Cela signifie que votre code Python sera exécuté dans le même processus qu'IRIS. Par ailleurs, chaque classe ObjectScript est une classe Python, de même pour les méthodes et les attributs et vice versa. 🥳 C'est génial !

Comment utiliser Embedded Python ?

Il y a 3 façons principales d'utiliser Embedded Python :

  • Utilisation la balise de langue dans ObjectScript
    • Méthode Foo() As %String [ Language = python ]
  • Utilisation de la fonction ##class(%SYS.Python).Import()
  • Utilisation de l'interpréteur python
    • python3 -c "import iris; print(iris.system.Version.GetVersion())"

Mais si vous voulez vous intéresser sérieusement à Embedded Python, vous aurez à éviter d'utiliser la balise de langue.

image

Pourquoi ?

  • Parce que ce n'est pas Pythonic
  • Parce que ce n'est pas ObjectScript non plus
  • Parce que vous n'avez pas de débogueur
  • Parce que vous n'avez pas de linter
  • Parce que vous n'avez pas de formateur
  • Parce que vous n'avez pas de cadre de test
  • Parce que vous n'avez pas de gestionnaire de paquets
  • Parce que vous mélangez 2 langues dans le même fichier
  • Parce que lorsque votre processus plante, vous n'avez pas de trace de pile
  • Parce que vous ne pouvez pas utiliser d'environnements virtuels ou d'environnements conda
  • ...

Ne vous méprenez pas, ça marche, ça peut être utile, si vous voulez tester quelque chose rapidement, mais à mon avis, ce n'est pas une bonne pratique.

Alors, qu'est-ce que j'ai appris de ces 2 années de Embedded Python, et comment l'utiliser de la bonne manière ?

Comment j'utilise Embedded Python

Je crois que vous avez deux options :

  • Utiliser les bibliothèques Python comme s'il s'agissait de classes ObjectScript
    • withqvec ##class(%SYS.Python).Import() function
  • Utiliser une première approche en python

Utilisation des bibliothèques et le code Python comme s'il s'agissait de classes ObjectScript

Vous voulez toujours utiliser Python dans votre code ObjectScript, mais vous ne voulez pas utiliser la balise de langue. Alors, que pouvez-vous faire ?

"Tout simplement" utilisez les bibliothèques et le code Python comme s'il s'agissait de classes ObjectScript. Prenons un exemplee :

Vous voulez utiliser la bibliothèque 'requests' ( c'est une bibliothèque pour faire des requêtes HTTP ) dans votre code ObjectScript.

Avec la balise de langue

ClassMethod Get() As %Status [ Language = python ]
{
	import requests

	url = "https://httpbin.org/get"
	# faire une requête d'obtention
	response = requests.get(url)
	# récupérer les données json de la réponse
	data = response.json()
	# itérer sur les données et imprimer les paires clé-valeur
	for key, value in data.items():
		print(key, ":", value)
}

Pourquoi je pense que ce n'est pas une bonne idée ?

Parce que vous mélangez 2 langues dans le même fichier, et que vous n'avez pas de débogueur, de linter, de formateur, etc. Si ce code plante, vous aurez du mal à le déboguer. Vous n'avez pas de trace de pile, et vous ne savez pas d'où vient l'erreur. Et vous n'avez pas d'auto-complétion.

Sans de balise de langue

ClassMethod Get() As %Status
{
	set status = $$$OK
    set url = "https://httpbin.org/get"
    // Importation du module Python "requests" en tant que classe ObjectScript
    set request = ##class(%SYS.Python).Import("requests")
    // Appel de la méthode get de la classe de requête
    set response = request.get(url)
    // Appel de la méthode json de la classe de réponse
	set data = response.json()
    // Ici, les données sont un dictionnaire Python
    // Pour parcourir un dictionnaire Python, vous devez utiliser la méthode dunder et items()
	// Importation du module Embedded Python
	set builtins = ##class(%SYS.Python).Import("builtins")
    // Ici, nous utilisons len du module intégré pour obtenir la longueur du dictionnaire
    For i = 0:1:builtins.len(data)-1 {
        // Maintenant, nous convertissons les éléments du dictionnaire en une liste, et nous obtenons la clé et la valeur en utilisant la méthode dunder __getitem__
		Write builtins.list(data.items())."__getitem__"(i)."__getitem__"(0),": ",builtins.list(data.items())."__getitem__"(i)."__getitem__"(1),!
	}
	quit status
}

Pourquoi je pense que c'est une bonne idée ?

Parce que vous utilisez Python comme s'il s'agissait d'ObjectScript. Vous importez la bibliothèque de requêtes comme une classe ObjectScript et vous l'utilisez comme une classe ObjectScript. Toute la logique est en ObjectScript, et vous utilisez Python comme une bibliothèque. Même pour la maintenance, c'est plus facile à lire et à comprendre, n'importe quel développeur ObjectScript peut comprendre ce code. L'inconvénient est que vous avez à savoir comment utiliser les méthodes de duners, et comment utiliser Python comme s'il s'agissait d'ObjectScript.

Conclusion

Croyez-moi, de cette manière vous obtiendrez un code plus robuste, et vous pourrez le déboguer facilement. Au début, cela semble difficile, mais vous découvrirez les avantages de l'apprentissage de Python plus rapidement que vous ne le pensez.

Utilisation de première approche en python

C'est la façon dont je préfère utiliser Embedded Python.

J'ai construit beaucoup d'outils en utilisant cette approche, et j'en suis très satisfait.

Quelques exemples :

Qu'est-ce qu'une première approche python ?

TIl n'y a qu'une seule règle : Le code Python doit être dans des fichiers .py, le code ObjectScript doit être dans des fichiers .cls

Comment y parvenir ?

L'idée est de créer des classes de wrappers ObjectScript pour appeler le code Python.


Prenons l'exemple de iris-fhir-python-strategy :

Exemple : iris-fhir-python-strategy

Tout d'abord, nous avons à comprendre comment fonctionne le serveur IRIS FHIR.

Chaque serveur IRIS FHIR met en œuvre une Stratégie.

Une Stratégie est un ensemble de deux classes :

SuperclassParamètres de sous-classe
HS.FHIRServer.API.InteractionsStrategyStrategyKey — Spécifie un identifiant unique pour la stratégie InteractionsStrategy.
InteractionsClass — Spécifie le nom de votre sous-classe Interactions.
HS.FHIRServer.API.RepoManagerStrategyClass — Spécifie le nom de votre sous-classe InteractionsStrategy.
StrategyKey — Spécifie un identifiant unique pour la stratégie InteractionsStrategy. Ceci doit correspondre au paramètre StrategyKey dans la sous-classe InteractionsStrategy.

Ces deux classes sont des classes abstraites Abtract Abstract.

  • HS.FHIRServer.API.InteractionsStrategy est une classe Abstract qui doit être mise en œuvre pour personnaliser le comportement du serveur FHIR.
  • HS.FHIRServer.API.RepoManager est une classe Abstract qui doit être mise en œuvre pour personnaliser le stockage du serveur FHIR.

Remarques

Pour notre exemple, nous nous concentrerons uniquement sur la classe HS.FHIRServer.API.InteractionsStrategy même si la classe HS.FHIRServer.API.RepoManager est également implémentée et obligatoire pour personnaliser le serveur FHIR. La classe HS.FHIRServer.API.RepoManager est mise en œuvre par HS.FHIRServer.Storage.Json.RepoManager qui est la mise en œuvre par défaut du serveur FHIR.

Où trouver le code

Tout le code source peut être trouvé dans le référentiel : iris-fhir-python-strategy Le dossier src contient les dossiers suivants :

  • python : contient le code python
  • cls : contient le code ObjectScript utilisé pour appeler le code python

Comment mettre en œuvre une Stratégie

Dans cette démonstration de faisabilité, nous nous intéresserons uniquement à la manière d'implémenter une Strategie en Python, et non à la manière de mettre en œuvre un RepoManager.

Pour mettre en œuvre une Strategie vous devez créer au moins deux classes :

  • Une classe qui hérite de la classe HS.FHIRServer.API.InteractionsStrategy
  • Une classe qui hérite de la classe HS.FHIRServer.API.Interactions

Mise en œuvre d'InteractionsStrategy

La classe HS.FHIRServer.API.InteractionsStrategy vise à personnaliser le comportement du serveur FHIR en remplaçant les méthodes suivantes :

  • GetMetadataResource : appelé pour récupérer les métadonnées du serveur FHIR
    • C'est la seule méthode que nous remplacerons dans cette preuve de concept

HS.FHIRServer.API.InteractionsStrategy a également deux paramètres :

  • StrategyKey : un identifiant unique pour la stratégie InteractionsStrategy
  • InteractionsClass : le nom de votre sous-classe Interactions

Mise en œuvre des Interactions

La classe HS.FHIRServer.API.Interactions vise à personnaliser le comportement du serveur FHIR en remplaçant les méthodes suivantes :

  • OnBeforeRequest : appelée avant l'envoi de la requête au serveur
  • OnAfterRequest : appelée après l'envoi de la requête au serveur
  • PostProcessRead : appelée une fois l'opération de lecture terminée
  • PostProcessSearch : appelée une fois l'opération de recherche terminée
  • Read : appelée pour lire une ressource
  • Add : appelée pour ajouter une ressource
  • Update : appelée pour mettre à jour une ressource
  • Delete : appelée pour supprimer une ressource
  • et bien d'autres...

Nous mettons en œuvre la classe HS.FHIRServer.API.Interactions dans la classe src/cls/FHIR/Python/Interactions.cls.

 
Spoiler
Class FHIR.Python.Interactions Extends (HS.FHIRServer.Storage.Json.Interactions, FHIR.Python.Helper)
{

Parameter OAuth2TokenHandlerClass As%String = "FHIR.Python.OAuth2Token";

Method %OnNew(pStrategy As HS.FHIRServer.Storage.Json.InteractionsStrategy) As%Status { // %OnNew est appelé lors de la création de l'objet.// Le paramètre pStrategy est l'objet de stratégie qui a créé cet objet.// La mise en œuvre par défaut ne fait rien// D'abord, le chemin d'accès à python est défini à partir d'une variable d'environnementset..PythonPath = $system.Util.GetEnviron("INTERACTION_PATH") // Définissez ensuite le nom de la classe python à partir de la variable d'environnementset..PythonClassname = $system.Util.GetEnviron("INTERACTION_CLASS") // Puis définissez le nom du module python à partir de la variable d'environnementset..PythonModule = $system.Util.GetEnviron("INTERACTION_MODULE")

<span class="hljs-keyword">if</span> (<span class="hljs-built_in">..PythonPath</span> = <span class="hljs-string">""</span>) || (<span class="hljs-built_in">..PythonClassname</span> = <span class="hljs-string">""</span>) || (<span class="hljs-built_in">..PythonModule</span> = <span class="hljs-string">""</span>) {
	<span class="hljs-comment">//quit ##super(pStrategy)</span>
	<span class="hljs-keyword">set</span> <span class="hljs-built_in">..PythonPath</span> = <span class="hljs-string">"/irisdev/app/src/python/"</span>
	<span class="hljs-keyword">set</span> <span class="hljs-built_in">..PythonClassname</span> = <span class="hljs-string">"CustomInteraction"</span>
	<span class="hljs-keyword">set</span> <span class="hljs-built_in">..PythonModule</span> = <span class="hljs-string">"custom"</span>
}


<span class="hljs-comment">// Définissez ensuite la classe python</span>
<span class="hljs-keyword">do</span> <span class="hljs-built_in">..SetPythonPath</span>(<span class="hljs-built_in">..PythonPath</span>)
<span class="hljs-keyword">set</span> <span class="hljs-built_in">..PythonClass</span> = <span class="hljs-keyword">##class</span>(FHIR.Python.Interactions).GetPythonInstance(<span class="hljs-built_in">..PythonModule</span>, <span class="hljs-built_in">..PythonClassname</span>)

<span class="hljs-keyword">quit</span> <span class="hljs-keyword">##super</span>(pStrategy)

}

Method OnBeforeRequest( pFHIRService As HS.FHIRServer.API.Service, pFHIRRequest As HS.FHIRServer.API.Data.Request, pTimeout As%Integer) { // OnBeforeRequest est appelée avant le traitement de chaque demande.if$ISOBJECT(..PythonClass) { set body = ##class(%SYS.Python).None() if pFHIRRequest.Json '= "" { set jsonLib = ##class(%SYS.Python).Import("json") set body = jsonLib.loads(pFHIRRequest.Json.%ToJSON()) } do..PythonClass."on_before_request"(pFHIRService, pFHIRRequest, body, pTimeout) } }

Method OnAfterRequest( pFHIRService As HS.FHIRServer.API.Service, pFHIRRequest As HS.FHIRServer.API.Data.Request, pFHIRResponse As HS.FHIRServer.API.Data.Response) { // OnAfterRequest est appelée après le traitement de chaque demande.if$ISOBJECT(..PythonClass) { set body = ##class(%SYS.Python).None() if pFHIRResponse.Json '= "" { set jsonLib = ##class(%SYS.Python).Import("json") set body = jsonLib.loads(pFHIRResponse.Json.%ToJSON()) } do..PythonClass."on_after_request"(pFHIRService, pFHIRRequest, pFHIRResponse, body) } }

Method PostProcessRead(pResourceObject As%DynamicObject) As%Boolean { // PostProcessRead est appelée après la lecture d'une ressource dans la base de données.// Renvoyez 1 pour indiquer que la ressource doit être incluse dans la réponse.// Renvoyez 0 pour indiquer que la ressource doit être exclue de la réponse.if$ISOBJECT(..PythonClass) { if pResourceObject '= "" { set jsonLib = ##class(%SYS.Python).Import("json") set body = jsonLib.loads(pResourceObject.%ToJSON()) } return..PythonClass."post_process_read"(body) } quit1 }

Method PostProcessSearch( pRS As HS.FHIRServer.Util.SearchResult, pResourceType As%String) As%Status { // PostProcessSearch est appelée après l'exécution d'une recherche.// Renvoyez $$$OK pour indiquer que la recherche a abouti.// Renvoyez un code d'erreur pour indiquer que la recherche a échoué.if$ISOBJECT(..PythonClass) { return..PythonClass."post_process_search"(pRS, pResourceType) } quit$$$OK }

Method Read( pResourceType As%String, pResourceId As%String, pVersionId As%String = "") As%DynamicObject { return##super(pResourceType, pResourceId, pVersionId) }

Method Add( pResourceObj As%DynamicObject, pResourceIdToAssign As%String = "", pHttpMethod = "POST") As%String { return##super(pResourceObj, pResourceIdToAssign, pHttpMethod) }

/// Renvoie de VersionId pour la version "supprimée" Method Delete( pResourceType As%String, pResourceId As%String) As%String { return##super(pResourceType, pResourceId) }

Method Update(pResourceObj As%DynamicObject) As%String { return##super(pResourceObj) }

}

La classe FHIR.Python.Interactions hérite de la classe HS.FHIRServer.Storage.Json.Interactions et de la classe FHIR.Python.Helper

La classe HS.FHIRServer.Storage.Json.Interactions est la mise en œuvre par défaut du serveur FHIR.

La classe FHIR.Python.Helper vise à aider à appeler du code Python à partir d'ObjectScript.

La classe FHIR.Python.Interactions remplacent les méthodes suivantes :

  • %OnNew : appelée lors de la création de l'objet
    • nous utilisons cette méthode pour définir le chemin python, le nom de la classe python et le nom du module python à partir des variables d'environnement
    • si les variables d'environnement ne sont pas définies, nous utilisons les valeurs par défaut
    • nous définissons également la classe python
    • nous appelons la méthode %OnNew de la classe parente
Method %OnNew(pStrategy As HS.FHIRServer.Storage.Json.InteractionsStrategy) As %Status
{
	// Définissez d'abord le chemin python à partir d'une variable d'environnement
	set ..PythonPath = $system.Util.GetEnviron("INTERACTION_PATH")
	// Puis définissez le nom de la classe python à partir de la variable d'environnement
	set ..PythonClassname = $system.Util.GetEnviron("INTERACTION_CLASS")
	// Puis définissez le nom du module python à partir de la variable d'environnement
	set ..PythonModule = $system.Util.GetEnviron("INTERACTION_MODULE")

	if (..PythonPath = "") || (..PythonClassname = "") || (..PythonModule = "") {
		// utilisez les valeurs par défaut
		set ..PythonPath = "/irisdev/app/src/python/"
		set ..PythonClassname = "CustomInteraction"
		set ..PythonModule = "custom"
	}

	// Ensuite, définissez la classe python
	do ..SetPythonPath(..PythonPath)
	set ..PythonClass = ..GetPythonInstance(..PythonModule, ..PythonClassname)

	quit ##super(pStrategy)
}
  • OnBeforeRequest : appelée avant l'envoi de la requête au serveur
    • nous appelons la méthode on_before_request de la classe python
    • nous passons l'objet HS.FHIRServer.API.Service, l'objet HS.FHIRServer.API.Data.Request, le corps de la requête et le timeout
Method OnBeforeRequest(
	pFHIRService As HS.FHIRServer.API.Service,
	pFHIRRequest As HS.FHIRServer.API.Data.Request,
	pTimeout As %Integer)
{
	// OnBeforeRequest est appelée avant le traitement de chaque requête.
	if $ISOBJECT(..PythonClass) {
		set body = ##class(%SYS.Python).None()
		if pFHIRRequest.Json '= "" {
			set jsonLib = ##class(%SYS.Python).Import("json")
			set body = jsonLib.loads(pFHIRRequest.Json.%ToJSON())
		}
		do ..PythonClass."on_before_request"(pFHIRService, pFHIRRequest, body, pTimeout)
	}
}
  • OnAfterRequest : appelée après l'envoi de la requête au serveur
    • nous appelons la méthode on_after_request de la classe python
    • nous passons l'objet HS.FHIRServer.API.Service, l'objet HS.FHIRServer.API.Data.Request, l'objet HS.FHIRServer.API.Data.Response et le corps de la réponse
Method OnAfterRequest(
	pFHIRService As HS.FHIRServer.API.Service,
	pFHIRRequest As HS.FHIRServer.API.Data.Request,
	pFHIRResponse As HS.FHIRServer.API.Data.Response)
{
	// OnAfterRequest est appelée après le traitement de chaque requête.
	if $ISOBJECT(..PythonClass) {
		set body = ##class(%SYS.Python).None()
		if pFHIRResponse.Json '= "" {
			set jsonLib = ##class(%SYS.Python).Import("json")
			set body = jsonLib.loads(pFHIRResponse.Json.%ToJSON())
		}
		do ..PythonClass."on_after_request"(pFHIRService, pFHIRRequest, pFHIRResponse, body)
	}
}
  • Et ainsi de suite...

Interactions en Python

La classeFHIR.Python.Interactions appelle les méthodes on_before_request, on_after_request, ... de la classe python.

Voici la classe python abstraite :

import abc
import iris

class Interaction(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def on_before_request(self, 
                          fhir_service:'iris.HS.FHIRServer.API.Service',
                          fhir_request:'iris.HS.FHIRServer.API.Data.Request',
                          body:dict,
                          timeout:int):
        """
        on_before_request is called before the request is sent to the server.
        param fhir_service: the fhir service object iris.HS.FHIRServer.API.Service
        param fhir_request: the fhir request object iris.FHIRServer.API.Data.Request
        param timeout: the timeout in seconds
        return: None
        """
        

    @abc.abstractmethod
    def on_after_request(self,
                         fhir_service:'iris.HS.FHIRServer.API.Service',
                         fhir_request:'iris.HS.FHIRServer.API.Data.Request',
                         fhir_response:'iris.HS.FHIRServer.API.Data.Response',
                         body:dict):
        """
        on_after_request is called after the request is sent to the server.
        param fhir_service: the fhir service object iris.HS.FHIRServer.API.Service
        param fhir_request: the fhir request object iris.FHIRServer.API.Data.Request
        param fhir_response: the fhir response object iris.FHIRServer.API.Data.Response
        return: None
        """
        

    @abc.abstractmethod
    def post_process_read(self,
                          fhir_object:dict) -> bool:
        """
        post_process_read is called after the read operation is done.
        param fhir_object: the fhir object
        return: True the resource should be returned to the client, False otherwise
        """
        

    @abc.abstractmethod
    def post_process_search(self,
                            rs:'iris.HS.FHIRServer.Util.SearchResult',
                            resource_type:str):
        """
        post_process_search is called after the search operation is done.
        param rs: the search result iris.HS.FHIRServer.Util.SearchResult
        param resource_type: the resource type
        return: None
        """

Mise en œuvre de la classe abstraite python

from FhirInteraction import Interaction

class CustomInteraction(Interaction):

    def on_before_request(self, fhir_service, fhir_request, body, timeout):
        #Extract the user and roles for this request
        #so consent can be evaluated.
        self.requesting_user = fhir_request.Username
        self.requesting_roles = fhir_request.Roles

    def on_after_request(self, fhir_service, fhir_request, fhir_response, body):
        #Clear the user and roles between requests.
        self.requesting_user = ""
        self.requesting_roles = ""

    def post_process_read(self, fhir_object):
        #Evaluate consent based on the resource and user/roles.
        #Returning 0 indicates this resource shouldn't be displayed - a 404 Not Found
        #will be returned to the user.
        return self.consent(fhir_object['resourceType'],
                        self.requesting_user,
                        self.requesting_roles)

    def post_process_search(self, rs, resource_type):
        #Iterate through each resource in the search set and evaluate
        #consent based on the resource and user/roles.
        #Each row marked as deleted and saved will be excluded from the Bundle.
        rs._SetIterator(0)
        while rs._Next():
            if not self.consent(rs.ResourceType,
                            self.requesting_user,
                            self.requesting_roles):
                #Mark the row as deleted and save it.
                rs.MarkAsDeleted()
                rs._SaveRow()

    def consent(self, resource_type, user, roles):
        #Example consent logic - only allow users with the role '%All' to see
        #Observation resources.
        if resource_type == 'Observation':
            if '%All' in roles:
                return True
            else:
                return False
        else:
            return True

Trop long, faisons un résumé

La classeFHIR.Python.Interactions est un wrapper pour appeler la classe python.

Les classes abstraites IRIS sont implémentées pour envelopper les classes abstraites python 🥳.

Cela nous aide à séparer le code python et le code ObjectScript et à bénéficier ainsi du meilleur des deux mondes.

0
0 69
Article Iryna Mykhailova · Sept 16, 2024 5m read

Dans la section précédente, nous avons exploré le processus d'installation et initié l'écriture de l'IRIS en Python natif. Nous allons maintenant examiner la traversée globale et nous intéresser aux objets de classe IRIS. 

get : cette fonction est utilisée pour obtenir des valeurs à partir du nœud de traversée.

deftraversal_firstlevel_subscript():"""
    ^mygbl(235)="test66,62" and ^mygbl(912)="test118,78"
    """for  i in irispy.node('^mygbl'):
        print(i, gbl_node.get(i,''))
0
0 41
Article Robert Cemper · Sept 15, 2024 3m read

Vous connaissez probablement cette situation :
il y a quelque temps, vous avez trouvé une fonction $ZU très spéciale pour
un type de problème spécifique. Une sorte de formule mystique.
Elle est devenue populaire et a été utilisée par de nombreux programmeurs
dans tout votre code et toutes vos installations .

Plusieurs versions et updates plus tard, vous êtes informé par ISC que
votre $ZU mystique est déprécié et n'est plus supporté. Et on vous conseille
de le remplacer par un nouveau $something(). 

0
0 56
Article Matthieu LAURENT · Sept 11, 2024 4m read

Bonjour,

je vous soumets cet article sous forme d'ADR (Architecture decision record) que nous avons rédigé dans nos équipes. L'objectif était d'être pertinent dans le choix de nos types de stream dans un contexte mirroré. A noter que des éléments peuvent être utiles même dans un environnement sans miroir.

Stockage des "Character Stream" dans un environnement mirroré

  • Auteur : Matthieu LAURENT et Vincent DHEILLY
  • Date : 2024-09-11
  • Version : IRIS 2024.1

Contexte

Les streams sont très utilisées au sein d'InterSystems notamment dans 2 contextes très différents :

  • Stocker des fichiers contenant des caractères (PDF par exemple)
  • Stocker des chaines de caractères très volumineuses

Nos analyses ont été menées avec des CharacterStream. Cependant, les éléments de conclusion et de bonnes pratiques sont transposables pour les BinaryStream.

Il existe 3 classes permettant de manipuler les streams de caractère.

  • %Stream.GlobalCharacter : Avec cette classe, les streams sont stockées dans une globale dédiée.
  • %Stream.FileCharacter : Avec cette classe, les streams sont stockées dans un fichier externe. Seul un pointeur vers le fichier est stocké dans une globale.
  • %Stream.TmpCharacter : Permet de stocker une stream temporairement mais ne peut être sauvegardée.

Solutions étudiées

Le but étant d'étudier le stockage pérenne, la classe TmpCharacter est totalement exclue des solutions viables et donc étudiées.

Utilisation des GlobalCharacter

Les GlobalCharacter sont stockés dans une globale dédiée. Elles sont donc par défaut stockées dans la base de données "DATA" du namespace dans laquelle elle se trouve.

AvantagesInconvénients
Les streams sont stockées dans la base et donc mirroréesLa taille conséquente des streams peuvent faire grossir la base
Aucune date de création des fichiers. Difficile d'isoler les "vieux" fichiers

Utilisation des FileCharacter

Les FileCharacter sont stockés dans des fichiers externes. Elles sont donc par défaut stockées dans un sous dossier stream de la base de données "DATA" du namespace dans laquelle elle se trouve. Seul un pointeur vers le fichier est stocké en base de données.

AvantagesInconvénients
Les fichiers sont stockés sur le serveur directement et ne font pas grossir la baseLes fichiers ne sont pas mirrorés. En cas de document métier important, il faut une solution tierce pour prévoir une copie temps réel de ces fichiers
Les fichiers étant datés, une purge est facilement envisageable

Décision

L'utilisation des FileCharacter stream n'est pas une solution viable de façon autonome dans un environnement mirroré. Il est donc impossible d'utiliser ce type dans le cas d'un document de gestion persisté.

Les streams stockés dans les messages n'ont par contre aucune utilité d'être stockées de façon pérenne.

La décision est d'utiliser dans tous les cas la classe GlobalCharacter lorsque l'attribut se trouve dans une classe persistante. Elle est aussi d'utiliser dans tous les cas un FileCharacter lorsque l'attribut fait partie d'une classe sérialisable.

A noter que dans les deux cas, il est impératif de définir une localisation de stockage.

Exemple avec un GlobalCharacter pour stocker un fichier Justificatif
Toujours préfixer avec le mot clef STREAM pour identifier les globales

Property Justificatif As %Stream.GlobalCharacter(LOCATION = "^STREAMJustificatif");

Exemple avec un FileCharacter pour stocker un fichier Justificatif.
Mettre dans un dossier indiquant que c'est des streams et que c'est temporaire

Property Justificatif As %Stream.FileCharacter(LOCATION = "/stream/tmp/justificatif");

Dans le cas des FileCharacter, prévoir dès l'implémentation une stratégie de purge des fichiers sur base de date.

Conséquences

En choisissant cette stratégie, voici les conséquences :

  1. Mirroring automatique : Toutes les streams persistantes sont automatiquement mirrorées

  2. Stockage maîtrisé : Les streams non pérennes peuvent être facilement purgées. Attention, ce n'est pas automatique, purge manuelle via le système de fichier nécessaire. Il est également possible de créer des tâches de purge directement via IRIS.

  3. Base de données dédiées aux fichiers : Les streams étant stockés dans des globales dédiées, il est possible de prévoir une base de données locales dédiées aux streams par namespace et de gérer avec une transposition de package.

Références

1
3 130
Article Robert Barbiaux · Sept 7, 2024 2m read

Introduction

Cet article décrit comment exécuter les tests unitaires lorsque la fonctionnalité intégrée à l'extension InterSystems ObjectScript n'est pas disponible car la version de IRIS utilisée est antérieure à 2023.3.

Au passage, l'extension ne semble pas supporter les tests pour les productions d'interopérabilité (classes qui étendent %UnitTest.TestProduction) , mais c'est seulement lié à la manière de déterminer si la classe comporte une méthode de test. Il suffit d'ajouter une méthode dont le nom commence par 'Test' pour que la classe soit reconnue par l'extension.

Préparation

0
0 98
Article Lorenzo Scalese · Juil 29, 2024 9m read

Supposons que vous ayez une application qui permette aux utilisateurs d'écrire des articles et de les commenter. (Attendez...  ça me dit quelque chose...)

L'objectif est de répertorier, pour un utilisateur donné, tous les messages publiés avec lesquels il a interagi, c'est-à-dire dont il est l'auteur ou qu'il a commentés. Comment faites-vous cela aussi vite que possible?

Voici à quoi pourraient ressembler les définitions de notre classe %Persistent comme point de départ (les définitions de stockage sont importantes, mais omises par souci de concision):

Class DC.Demo.Post Extends%Persistent
{

Property Title As%String(MAXLEN = 255) [ Required ];Property Body As%String(MAXLEN = "") [ Required ];Property Author As DC.Demo.Usr [ Required ];Property Draft As%Boolean [ InitialExpression = 1, Required ]; }

Class DC.Demo.Usr Extends%Persistent {

Property Name As%String [ Required ]; }

Class DC.Demo.Comment Extends%Persistent {

Property Post As DC.Demo.Post [ Required ];Property Usr As DC.Demo.Usr [ Required ];Property Comment As%String(MAXLEN = "") [ Required ]; }

Et notre requête, comme point de départ:

selectIDfrom DC_Demo.Post where (Author = ? orIDin (selectdistinct Post from DC_Demo.Comment where Usr = ?)) and Draft = 0

L' approche naïve consisterait simplement à:

  • Ajoutez des indices bitmap sur Author et Draft dans DC.Demo.Post.
  • Ajoutez un index standard sur (Usr, Post) dans DC.Démo.Commentaire..

Et ce n'est pas du tout une mauvaise approche! Pour certains cas d'utilisation, elle peut même être " suffisante ". Que va faire IRIS SQL sous le capot ? Nous pouvons examiner le plan de requête:

 Générer un flux de valeurs idkey en utilisant la combinaison multi-index:
     ((bitmap index DC_Demo.Post.Draft) INTERSECT ((bitmap index DC_Demo.Post.Author) UNION (bitmap index DC_Demo.Post.Draft)))
 Pour chaque valeur d'idkey:
     Affichage de la ligne.

Sous-requête C:
 Lecture de la carte d'index DC_Demo.Comment.UsrPost, en utilisant l'Usr et le Post donnés, et en bouclant sur l'ID.
 Pour chaque ligne:
     Détermination du résultat de la sous-requête.

Ce n'est pas dramatique. Supposons qu'il y ait 50000 publications et que chaque utilisateur ait commenté 500 d'entre elles en moyenne. Combien de références globales cette requête implique-t-elle? Eh bien, au minimum, trois pour les index bitmap, et environ 500 dans la sous-requête (itération sur l'index UsrPost). Il est clair que la sous-requête est le goulot d'étranglement. Comment pouvons-nous la rendre plus rapide?

La réponse est d'utiliser un index fonctionnel (une sous-classe de %Library.FunctionalIndex) avec la %FIND condition de prédicat (et une sous-classe de %SQL.AbstractFind). Notre index fonctionnel sera défini dans la classe Comment (commentaires), mais ne contiendra pas les identifiants des commentaires comme le ferait un index bitmap classique. Au lieu de cela, pour chaque utilisateur, il aura un bitmap d'identifiants de Post pour lesquels cet utilisateur a au moins un commentaire. Nous pouvons ensuite combiner très efficacement cette image bitmap avec d'autres conditions indexées par image bitmap dans la table Post. Il est évident que cela entraîne une certaine surcharge pour l'insertion/mise à jour/suppression de nouveaux commentaires, mais l'avantage en termes de performances pour les lectures peut la compenser.

Un index fonctionnel doit définir le comportement de l'index pour les opérations d'insertion, de mise à jour et de suppression, et mettre en œuvre quelques autres méthodes (purge, début de tri, fin de tri). Une Une implémentation %SQL.AbstractFind doit mettre en œuvre des méthodes pour parcourir et récupérer des fragments d'index bitmap. Pour s'amuser, nous utiliserons une implémentation générique %SQL.AbstractFind qui examine une structure d'index bitmap standard (avec une référence globale à son nœud racine).

Remarque - si vous ne savez pas ce qu'est un "fragment de bitmap" ou si vous avez l'impression que tout cela est du Chinois, nous vous conseillons de lire la documentation  sur les index de bitmap, en particulier les parties relatives à leur structure et à leur manipulation.

Passons au code, DC.Demo.ExistenceIndex est notre index fonctionnel:

Include %IFInclude/// Données:/// /// <code>/// Class Demo.ClassC Extends %Persistent/// {/// Properiété PropA As Demo.ClassA;/// Properiété PropB As Demo.ClassB;/// Index BValuesForA On (PropA, PropB) As DC.Demo.ExistenceIndex;/// }/// </code>/// /// Appel à partir de SQL comme suit, étant donné une valeur de PropA de 21532, pour retourner les valeurs de PropB associées à PropA=21532 dans ClassB:/// <code>/// selectionner * de Demo.ClassC où ID %FIND Demo.ClassB_BValuesForAFind(21532) et <other-bitmap-index-conditions>/// </code>Class DC.Demo.ExistenceIndex Extends%Library.FunctionalIndex [ System = 3 ]
{

/// Retourne une sous-classe %SQL.AbstractFind appropriée pour cet index fonctionnelClassMethod Find(pSearch As%Binary) As%Library.Binary [ CodeMode = generator, ServerOnly = 1, SqlProc ] { If (%mode '= "method") { Set tIdxGlobal = ..IndexLocationForCompile(%class,%property) Set name = $Name(@tIdxGlobal@("id")) Set name = $Replace(name,$$$QUOTE("id"),"pSearch") $$$GENERATE(" Quit ##class(DC.Demo.ReferenceFind).%New($Name("name"))") } }

/// Retrouve un "true" s'il existe un enregistrement avec (prop1val, prop2val).ClassMethod Exists(prop1val, prop2val) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { Set indexProp1 = $$$comSubMemberKeyGet(%class,$$$cCLASSindex,%property,$$$cINDEXproperty,1,$$$cINDEXPROPproperty) Set indexProp2 = $$$comSubMemberKeyGet(%class,$$$cCLASSindex,%property,$$$cINDEXproperty,2,$$$cINDEXPROPproperty) Set table = $$$comClassKeyGet(%class,$$$cCLASSsqlschemaname)"."$$$comClassKeyGet(%class,$$$cCLASSsqltablename) Set prop1 = $$$comMemberKeyGet(%class,$$$cCLASSproperty,indexProp1,$$$cPROPsqlfieldname) If (prop1 = "") { Set prop1 = indexProp1 } Set prop2 = $$$comMemberKeyGet(%class,$$$cCLASSproperty,indexProp2,$$$cPROPsqlfieldname) If (prop2 = "") { Set prop2 = indexProp2 } $$$GENERATE(" &sql(select top 1 1 from "table" where "prop1" = :prop1val and "prop2" = :prop2val)") $$$GENERATE(" Quit (SQLCODE = 0)") } }

/// Cette méthode est invoquée lorsqu'une nouvelle instance d'une classe est insérée dans la base de donnéesClassMethod InsertIndex(pID As%CacheString, pArg... As%Binary) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { Set tIdxGlobal = ..IndexLocationForCompile(%class,%property) Set name = $Name(@tIdxGlobal@("id","chunk")) Set name = $Replace(name,$$$QUOTE("chunk"),"chunk")

    <span class="hljs-built_in">$$$GENERATE</span>(<span class="hljs-string">" If ($Get(pArg(1)) '= """") &amp;&amp; ($Get(pArg(2)) '= """") { "</span>)
    <span class="hljs-built_in">$$$GENERATE</span>(<span class="hljs-string">"  $$$IFBITOFFPOS(pArg(2),chunk,position)"</span>)
    <span class="hljs-built_in">$$$GENERATE</span>(<span class="hljs-string">"  Set $Bit("</span>_<span class="hljs-built_in">$Replace</span>(name,<span class="hljs-built_in">$$$QUOTE</span>(<span class="hljs-string">"id"</span>),<span class="hljs-string">"pArg(1)"</span>)_<span class="hljs-string">",position) = 1"</span>)
    <span class="hljs-built_in">$$$GENERATE</span>(<span class="hljs-string">" }"</span>)
}

}

/// Cette méthode est invoquée lorsqu'une instance existante d'une classe est mise à jour.ClassMethod UpdateIndex(pID As%CacheString, pArg... As%Binary) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { Set tIdxGlobal = ..IndexLocationForCompile(%class,%property) Set name = $Name(@tIdxGlobal@("id","chunk")) Set name = $Replace(name,$$$QUOTE("chunk"),"chunk") $$$GENERATE(" If ($Get(pArg(3)) '= """") && ($Get(pArg(4)) '= """") { ") $$$GENERATE(" $$$IFBITOFFPOS(pArg(4),chunk,position)") $$$GENERATE(" Set $Bit("$Replace(name,$$$QUOTE("id"),"pArg(3)")",position) = .."%property"Exists(pArg(3),pArg(4))") $$$GENERATE(" }")

    <span class="hljs-built_in">$$$GENERATE</span>(<span class="hljs-string">" If ($Get(pArg(1)) '= """") &amp;&amp; ($Get(pArg(2)) '= """") { "</span>)
    <span class="hljs-built_in">$$$GENERATE</span>(<span class="hljs-string">"  $$$IFBITOFFPOS(pArg(2),chunk,position)"</span>)
    <span class="hljs-built_in">$$$GENERATE</span>(<span class="hljs-string">"  Set $Bit("</span>_<span class="hljs-built_in">$Replace</span>(name,<span class="hljs-built_in">$$$QUOTE</span>(<span class="hljs-string">"id"</span>),<span class="hljs-string">"pArg(1)"</span>)_<span class="hljs-string">",position) = 1"</span>)
    <span class="hljs-built_in">$$$GENERATE</span>(<span class="hljs-string">" }"</span>)
}

}

/// Cette méthode est invoquée lorsqu'une instance existante d'une classe est supprimée.ClassMethod DeleteIndex(pID As%CacheString, pArg... As%Binary) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { Set tIdxGlobal = ..IndexLocationForCompile(%class,%property) Set name = $Name(@tIdxGlobal@("id","chunk")) Set name = $Replace(name,$$$QUOTE("chunk"),"chunk") $$$GENERATE(" If ($Get(pArg(1)) '= """") && ($Get(pArg(2)) '= """") { ") $$$GENERATE(" $$$IFBITOFFPOS(pArg(2),chunk,position)") $$$GENERATE(" Set $Bit("$Replace(name,$$$QUOTE("id"),"pArg(1)")",position) = .."%property"Exists(pArg(1),pArg(2))") $$$GENERATE(" }") } }

/// Méthode auxiliaire permettant d'obtenir la référence globale pour le stockage d'un index donné.ClassMethod IndexLocationForCompile(pClassName As%String, pIndexName As%String) As%String { Set tStorage = ##class(%Dictionary.ClassDefinition).%OpenId(pClassName).Storages.GetAt(1).IndexLocation Quit$Name(@tStorage@(pIndexName)) }

/// Purge l'indexClassMethod PurgeIndex() [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { Set tIdxGlobal = ..IndexLocationForCompile(%class,%property) $$$GENERATE(" Kill " _ tIdxGlobal) } }

/// Appelle SortBegin avant les opérations de masseClassMethod SortBeginIndex() [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { Set tIdxGlobal = ..IndexLocationForCompile(%class,%property) // No-op$$$GENERATE(" Quit") } }

/// Appelle SortEnd après les opérations de masseClassMethod SortEndIndex(pCommit As%Integer = 1) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { Set tIdxGlobal = ..IndexLocationForCompile(%class,%property) // No-op$$$GENERATE(" Quit") } }

}

DC.Demo.ReferenceFind est notre implémentation générique de %SQL.AbstractFind qui permet de visualiser un tableau de fragments de cartes bitmap:

/// Utility class to wrap use of %SQL.AbstractFind against a bitmap index global referenceClass DC.Demo.ReferenceFind Extends%SQL.AbstractFind [ System = 3 ]
{

/// Référence globale à itérer sur / prendre en compte pour les méthodes d'opération %SQL.AbstractFind %FINDProperty reference As%String [ Private ]; Method %OnNew(pReference As%String) As%Status [ Private, ServerOnly = 1 ] { Set..reference = pReference Quit$$$OK }

Method NextChunk(ByRef pChunk As%Integer = "") As%Binary { Set pChunk=$Order(@i%reference@(pChunk),1,tChunkData) While pChunk'="",$bitcount(tChunkData)=0 { Set pChunk=$Order(@i%reference@(pChunk),1,tChunkData) } Return$Get(tChunkData) }

Method PreviousChunk(ByRef pChunk As%Integer = "") As%Binary { Set pChunk=$Order(@i%reference@(pChunk),-1,tChunkData) While pChunk'="",$bitcount(tChunkData)=0 { Set pChunk=$Order(@i%reference@(pChunk),-1,tChunkData) } Return$Get(tChunkData) }

Method GetChunk(pChunk As%Integer) As%Binary { If$Data(@i%reference@(pChunk),tChunkData) { Return tChunkData } Else { Return"" } }

}

Ainsi, DC.Demo.Comment ressemble maintenant à ceci, avec deux indices bitmap ajoutés (et les clés externes appropriées pour faire bonne mesure).:

Class DC.Demo.Comment Extends%Persistent
{

Property Post As DC.Demo.Post [ Required ];Property Usr As DC.Demo.Usr [ Required ];Property Comment As%String(MAXLEN = "") [ Required ]; Index Usr On Usr [ Type = bitmap ]; Index Post On Post [ Type = bitmap ]; Index UserPosts On (Usr, Post) As DC.Demo.ExistenceIndex; ForeignKey UsrKey(Usr) References DC.Demo.Usr(); ForeignKey PostKey(Post) References DC.Demo.Post() [ OnDelete = cascade ]; }

Notre requête SQL devient alors:

selectIDfrom DC_Demo.Post where (Author = ? orID %FIND DC_Demo.Comment_UserPostsFind(?)) and Draft = 0

Et le plan de requête devient:

 Générer un flux de valeurs idkey en utilisant la combinaison multi-index:
     ((bitmap index DC_Demo.Post.Draft) INTERSECT ((bitmap index DC_Demo.Post.Author) UNION (given bitmap filter for DC_Demo.Post.%ID)))
 Pour chaque valeur d'idkey:
     Afficher la ligne.

Combien de références globales y a-t-il maintenant ? Une pour Author bitmap, une pour Draft bitmap, et une pour le nœud d'index de bitmap "posts for a given user" dans DC.Demo.Comment. Désormais, la liste "Quels sont les publications auxquelles j'ai participé?" ne sera pas (autant) ralentie si vous commentez de plus en plus!

Avertissement : malheureusement, la communauté des développeurs n'est pas réellement soutenue par InterSystems IRIS, vous ne devriez donc probablement pas faire autant de commentaires.

0
0 37
Article Iryna Mykhailova · Juin 27, 2024 6m read

Introduction

Dans les prochaines semaines, mes collègues prévoient de commencer à utiliser VSCode pour coder avec les produits d'InterSystems. Parmi les nombreux avantages, je soulignerais la possibilité de se connecter facilement à d'autres technologies, telles que GitHub. En outre, VSCode propose également une vaste boutique d'extensions, où vous pouvez trouver de nombreux add-ons gratuits qui rendent le codage plus rapide et plus efficace. Enfin, pour conquérir le cœur de tous les développeurs, c'est open source.

Cela étant dit, commençons le tutoriel. N'hésitez pas à sauter certaines étapes si vous vous sentez à l'aise pour les réaliser sans aide.

Étape 1 - Téléchargement et installation de VSCode

Recherchez "VSCode download" ou cliquez sur this linkce lien pour télécharger à partir de la page officielle. Sélectionnez l'installation correcte pour votre ordinateur et exécutez le fichier téléchargé.

Une fois l'exécutable lancé, il vous sera demandé d'accepter la licence, de sélectionner quelques préférences (qui sont très personnelles et dont je ne parlerai pas dans ce texte), et de procéder à l'installation. 
 

Étape 2 - Installation des extensions InterSystems

Ouvrez votre VSCode dans l'onglet Extensions et recherchez le paquet ObjectScript Extension Pack dans la boutique.

Installez le paquet complet.
 

Étape 3 - Configuration de votre VSCode pour vos instances

Une fois les extensions installées, vous devriez avoir un onglet InterSystems. Vous pouvez le sélectionner et cliquer sur "+" dans la section des serveurs, comme indiqué ci-dessous, et vous serez invité à fournir des informations sur la connexion, telles que l'hôte, le port, le nom d'utilisateur et le mot de passe.

Sinon, vous pouvez aller dans votre fichier VSCode general settings.json, que vous pouvez trouver en cliquant sur l'engrenage de gestion   sur le côté gauche en bas de l'écran, en sélectionnant les configurations, et en cliquant sur "Ouvrir les paramètres (JSON)" ("Open Settings (JSON)")    sur le côté droit en haut de l'écran.

Là, vous pouvez coller le fragment JSON suivant, en le modifiant pour qu'il pointe vers votre instance.

"intersystems.servers": {
        "[YOUR SERVER NAME]": {
            "webServer": {
                "scheme": "http",
                "host": "127.0.0.1",
                "port": 53472
            },
            "username": "_system"
        }

 

Étape 4.1 - Connexion à un serveur et à un espace de noms pour le développement local

Toujours dans l'onglet InterSystems, sélectionnez la section Explorer et cliquez sur le bouton "Choisir le serveur et l'espace de noms" (Choose server and namespace).

Vous devriez être invité à choisir parmi les serveurs ajoutés lors de la dernière étape, votre nom d'utilisateur et votre mot de passe, ainsi qu'un espace de noms.

Ensuite, toujours dans le même onglet, vous pouvez utiliser la section d'exploration (Explore) ou la section des projets (Projects) pour choisir les classes et/ou les projets que vous souhaitez exporter. Si vous choisissez ce type de développement, vous pouvez ouvrir les fichiers exportés dans un espace de travail VSCode et vous connecter à GitHub, par exemple.

Étape 4.2 - Connection à un serveur et à un espace de noms pour développer sur le serveur (ma préférence)

Ouvrez l'onglet Intersystèmes dans la section Serveurs et choisissez un serveur.

Vous pouvez choisir un projet et cliquer sur le crayon pour l'ouvrir en mode édition sur votre espace de travail ou ou sur l'œil pour l'ouvrir en mode lecture.

De même, vous pouvez choisir entre les mêmes options directement sur l'espace de noms pour ouvrir tous ses fichiers dans l'espace de travail (ma préférence)

Dans les deux cas, il se peut que l'on vous demande à nouveau un nom d'utilisateur et un mot de passe. 
 

Étape 5 - Codage

Dans l'onglet de l'explorateur, après la dernière étape, votre espace de travail ressemble à l'image ci-dessous.

Vous pouvez créer un fichier et de nouveaux dossiers en cliquant avec le bouton droit de votre souris sur un répertoire, en sélectionnant "nouveau fichier" ("new file") et en le nommant avec le modèle FOLDERNAME.FILENAME.cls. En d'autres termes, vous devez nommer les dossiers en séparant les noms par des points. Le nom précédant le dernier point sera le nom du fichier, et le nom suivant le dernier point sera l'extension des fichiers COS, cls.
Par exemple, vous pouvez nommer votre fichier comme dans l'image ci-dessous, "folderone.foldertwo.filename.cls", dans le dossier User, et cliquer sur "Enter".

Ainsi, le répertoire et le fichier suivants devraient être créés automatiquement.

Maintenant, vous êtes prêt à coder comme vous le feriez en Studio.

 

Quelques conseils supplémentaires

- Vous pouvez appuyer sur Ctrl+Shift+V pour ouvrir les fichiers .int.

- Attention: contrairement à Studio, Ctrl + S dans VSCode compilera le fichier en plus de l'enregistrer.

- Maintenant, je vais vous montrer la meilleure fonctionnalité que j'ai rencontrée jusqu'à présent en codant avec VSCode. C'est de l'or en barre. Imaginez que vous ayez une instance de développeur pour les premières phases d'un projet, une autre instance pour les tests avec vos clients, et peut-être même encore une autre pour la production. Il peut être fastidieux de migrer les fichiers et les modifications entre les instances de Studio. Cependant, si vous utilisez VSCode, c'est assez simple.

Dans l'onglet Explorateur (Explorer), ouvrez la source et le fichier cible. Par exemple, si j'ai un fichier définissant des  données persistantes, et que j'ai ajouté des champs sur mon serveur de développement, et que je souhaite les transférer sur mon serveur de production, j'ouvrirai le fichier de développement et le fichier de production pour les mêmes données persistantes (une discussion sur l'ouverture de plusieurs serveurs ou espaces de noms est abordée dans la rubrique suivante).

Une fois que les deux sont ouverts, ils apparaissent dans la section "Éditeurs ouverts" ("Open Editors").

Sélectionnez le fichier source avec le bouton droit de votre souris et cliquez sur "Sélectionner pour comparer" ("Select to compare"). Cliquez avec le bouton droit sur le fichier cible et choisissez "Comparer avec sélectionné" ("Compare with selected").
BOUM! Vous aurez alors ce magnifique fichier de comparaison ouvert, comme illustré ci-dessous.

La barre située à droite de l'écran indique les différences entre les fichiers, et vous pouvez l'utiliser pour naviguer simultanément entre eux. Les couleurs rouge et verte signifient que quelque chose diffère dans les fichiers. Le rouge signifie que le fichier source contient du texte qui ne figure pas dans le fichier cible. Le vert signifie que le fichier cible contient du texte qui ne figure pas dans le fichier source. 

Cliquez sur les flèches du milieu pour transférer les modifications de la source vers la cible.

En outre, cliquez sur la flèche biface dans le coin supérieur droit de l'écran pour changer les côtés des fichiers : la source sera la cible et vice versa.

En appuyant sur Ctrl+S lorsque l'éditeur est ouvert, les deux fichiers sont enregistrés et compilés.

- Vous pouvez ouvrir plusieurs instances et espaces de noms à la fois dans votre espace de travail, tant que votre ordinateur peut le gérer. Cependant, pour que vous puissiez vous faire une idée, ces dernières semaines, j'ai travaillé avec deux espaces de noms lourds provenant de différentes instances, Studio, Microsoft Teams (qui est scandaleusement lourd), et deux autres applications pour se connecter à la base de données et aux API, le tout en même temps, avec un ordinateur ayant la configuration suivante: 8 Go de RAM et CPU de 2,10 GHz. La seule chose qui a parfois été un peu lente, c'est Studio.

Poursuite de la discussion

J'aimerais parler de débogage avec VSCode. Je n'ai pas encore trouvé quelqu'un qui l'apprécie plus que les outils de débogage de Studio. Personnellement moi, je ne suis pas très habitué à utiliser les outils de débogage VSCode avec les produits InterSystems, et même si Studio est beaucoup plus lourd, je préfère le laisser ouvert lorsque j'ai besoin de déboguer. Comment combinez-vous ces technologies? Quels sont les outils que vous préférez?

0
0 83
Article Pierre LaFay · Juin 26, 2024 2m read

InterSystems FAQ rubric

Le type TIMESTAMP correspond au type de données %Library.TimeStamp (=%TimeStamp) dans les produits InterSystems, et le format est YYYY-MM-DD HH:MM:SS.nnnnnnnnn.

Si vous souhaitez modifier la précision après la virgule, définissez-la à l'aide de la méthode suivante.

1) Définir la précision à l'échelle du système

Portail de gestion : [Administration du système] > [Configuration] > [Paramètres SQL et objets] > [Paramètres SQL généraux] 
Précision de l'heure par défaut pour GETDATE(), CURRENT_TIME, CURRENT_TIMESTAMP. Vous pouvez spécifier le nombre de chiffres entre 0 et 9.

0
0 45
Annonce Irène Mykhailova · Juin 11, 2024

Bonjour à tous,

L'équipe de certification d'InterSystems Learning Services développe un examen de certification InterSystems ObjectScript Specialist, et nous contactons la communauté pour obtenir des commentaires qui nous aideront à évaluer et à établir le contenu de cet examen. Veuillez noter qu'il s'agit de l'un des deux examens en cours de développement pour remplacer notre examen InterSystems IRIS Core Solutions Developer. Vous pouvez trouver plus de détails sur notre examen InterSystems IRIS Developer Professional ici.

0
0 36
InterSystems officiel Guillaume Rongier · Juin 10, 2024

Les mises à jour récentes d'Intersystems Language Server introduisent de nombreuses améliorations significatives visant à améliorer l'expérience et la productivité des développeurs. Je vais parler de certains des principaux ici, tandis que la liste complète, y compris de nombreuses corrections de bugs, peut être trouvée dans le CHANGELOG de Language Server.

Descriptions détaillées des erreurs de syntaxe

0
0 75
Article Iryna Mykhailova · Avr 29, 2024 12m read

Le défi du Lo-Code

Imaginons la scène.  Vous travaillez tranquillement au sein de Widgets Direct, le premier détaillant de Widgets et d'accessoires pour Widgets sur Internet.   Votre patron vous annonce une nouvelle désastreuse : certains clients ne sont peut-être pas satisfaits de leurs widgets et nous avons besoin d'une application d'assistance pour assurer le suivi de ces réclamations.   Pour rendre les choses plus intéressantes, il veut que cette application ait une très faible empreinte de code et vous demande de livrer une application en moins de 150 lignes de code à l'aide d'InterSystems IRIS.  Est-ce possible?

Avertissement : cet article présente la construction d'une application très basique et omet, par souci de concision, des éléments de détail tels que la Sécurité et la Gestion des erreurs.   Cette application ne doit être utilisée qu'à titre de référence ni pour une application de production.  Cet article utilise IRIS 2023.1 comme plate-forme de données, certaines fonctionnalités décrites ne sont pas disponibles dans les versions antérieures

Étape 1 – Définition d'un modèle de données

Nous commençons par définir un nouvel espace de noms propre - avec une base de données de codes et de données. Bien que tout soit regroupé dans une seule base de données, il est utile de diviser ces bases pour permettre l'actualisation des données.

Notre système d'assistance a besoin de 3 classes de base : un objet Ticket qui peut contenir des actions pour documenter les interactions entre un conseiller du personnel UserAccount et un contact client UserAccount.  Définissons ces classes avec quelques propriétés de base:

19 lignes de code – et nous avons notre modèle de données complet!  Nous avons défini 2 classes comme Persistent afin qu'elles puissent être sauvegardées dans la base de données, et nous héritons également de %JSON.Adapter, ce qui nous permet d'importer et d'exporter très facilement nos objets au format JSON.  En guise de test, nous configurons notre premier utilisateur dans Terminal, nous le sauvegardons et nous vérifions que l'application JSONExport fonctionne correctement

Tout cela semble bon.  Le patron nous a laissé un fichier csv avec une liste d'employés et de clients.   Nous pourrions écrire un code pour analyser ce fichier et le charger, mais y a-t-il un moyen plus simple?

Étape 2 – TÉLÉCHARGEMENT DES DONNÉES

InterSystems IRIS fournit une instruction de chargement de données (LOAD DATA) simple à utiliser en SQL qui permet de télécharger facilement des données à partir d'un fichier CSV, y compris les options permettant d'analyser les en-têtes et de renommer les champs.  Utilisons-la pour télécharger notre table d'utilisateurs:

Nous pouvons utiliser les étiquettes d'en-tête pour extraire ces données et les télécharger dans la base de données de la manière suivante:

Les 300 lignes ont été importées en une seule commande.   Les 4 lignes supplémentaires de code portent notre compte courant à 23 lignes de code écrites.   Nous pouvons rapidement vérifier que ces enregistrements sont corrects avec une sélection SQL de base

Nous avons maintenant nos données de départ, construisons donc quelques API de base pour permettre à un front-end d'être connecté.  Nous construirons notre API comme un service REST qui sert et accepte JSON.

Étape 3 – Création d'une API REST

InterSystems IRIS fournit un support REST natif par le biais de l'héritage de la classe %CSP.REST, nous allons donc créer une classe REST.Dispatch et hériter de %CSP.REST.   Cette classe est composée de deux sections: une UrlMap XData qui associe les URL et les Verbes aux méthodes, et les méthodes qui sont appelées par ces Urls.

Notre Produit minimum viable nécessite 4 opérations: la récupération de la liste des utilisateurs pour le personnel ou les clients, la récupération des derniers tickets collectés, la récupération d'un ticket unique par son numéro d'identification, et enfin la création d'un nouveau ticket.   Nous définissons nos verbes, et puis les méthodes.

GetUserList est un Curseur de base SQL intégré qui fournit les données directement en JSON.  Nous pouvons alors analyser ces données avec la fonctionnalité JSON native, les placer dans un tableau JSON et les servir en tant que corps de la réponse.  Nous passons la variable "staff" de l'URL directement à la requête pour modifier le contexte des données.

TicketSummary est presque identique, mais la requête accède alors à la table des tickets

TicketSummary est le service le plus simple.  Nous ouvrons l'objet par ID, et écrivons le %JSONExport intégré à la sortie.  Si l'objet ne se charge pas, alors nous écrivons un paquet d'erreurs

Enfin, notre méthode d'UploadTicket est la plus complexe. Nous devons lire le payload de l'objet de la requête, l'analyser en JSON, puis l'appliquer à une nouvelle instance de Ticket en utilisant %JSONImport.  Nous définissons également l'OpenDate et l'OpenTime à partir de l'heure actuelle, au lieu de les attendre en entrée.  Après une sauvegarde réussie, nous renvoyons la représentation JSON de l'objet, ou si l'objet ne se télécharge pas, nous renvoyons une erreur.

Avec ces services, nous ajoutons 60 lignes de code supplémentaires à notre total.  Nous atteignons maintenant un total de 89 lignes de code pour cette application

Nous devons maintenant créer une application Web sous Sécurité>Applications.  Celle-ci doit être définie comme une application de type REST, et le NOMCLASSE (Classname) doit être défini comme la classe de répartition (Dispatch) que nous venons de créer (Remarque: vous devrez accorder un rôle de sécurité approprié à cette application afin qu'elle puisse accéder au code et aux données).  Après avoir sauvegardé, les services REST peuvent maintenant être appelés à partir de l'URL que vous avez définie

Nous pouvons appeler l'UserList pour vérifier

Nous sommes maintenant prêts à créer des données.  Utilisons notre client REST pour envoyer un payload au service de création de tickets.  Nous fournissons un mot-clé, une description, un conseiller (Advisor) et un contact (Contact), et nous recevons en retour le fichier JSON du ticket que nous avons créé, y compris l'OpenDate et le TicketId

Nous avons maintenant notre Produit minimum viable.  En utilisant un constructeur de formulaire frontal de notre choix, nous pouvons maintenant envoyer et recevoir des informations sur les tickets via nos services REST.

Étape 4 – Exigences d'interopérabilité

Vous avez maintenant construit une application de ticketing de base en seulement 89 lignes de code écrites.   Votre patron doit être impressionné?   Oui, mais il a de mauvaises nouvelles.  Vous avez oublié une exigence.   Widgets Direct a un contrat spécial avec les régions francophones et tous les billets rédigés en français doivent être envoyés à Mme Bettie Francis pour un premier examen.   Heureusement, vous avez un collègue qui a suivi l'excellent article de Robert Luman "Sur la prise en charge du langage naturel par Python" ("Python Natural Language Support") et qui a créé un service REST capable d'accepter un échantillon de texte et d'en identifier la langue.   Pouvons-nous utiliser l'Interopérabilité d'InterSystems IRIS pour appeler ce service et mettre automatiquement à jour le conseiller de Mme Francis lorsque le texte est en français?

Nous devons commencer par la création de Classes de messages afin d'avoir un moyen d'envoyer et de recevoir nos demandes.  Nous avons besoin d'une requête qui contiendra l'identifiant du ticket et le texte de l'échantillon, et d'une réponse qui renverra le Code de la langue et la Description. Ceux-ci hériteront d' Ens. Request et d'Ens. Response

6 autres lignes de code écrites nous permettent d'atteindre 95 LOC.  Nous devons maintenant créer notre opération, qui enverra la requête au service de votre collègue et récupérera la réponse.  Nous définissons une opération Outbound, avec des propriétés pour le serveur et l'URL, et nous les exposons à la configuration de l'utilisateur en les incluant dans le paramètre SETTINGS.   Cela nous permettra de mettre facilement à jour la requête si le chemin d'accès au serveur change.   Nous créons une méthode d'aide pour configurer une requête HTTPRequest, puis nous l'utilisons pour appeler le service et remplir notre réponse

27 lignes de code supplémentaires nous amènent à plus de 100, nous avons maintenant 122 lignes écrites.   Nous devons maintenant configurer cette classe dans notre production Ensemble.  Nous devons maintenant configurer cette classe dans notre production Ensemble. Accédez à la configuration de la production sous Interopérabilité, et cliquez sur Ajouter (Add) sous l'En-tête Opérations (Operations Header).  Configurez votre opération avec le nom de la classe et un nom d'affichage

Nous pouvons ensuite cliquer dessus pour accéder aux paramètres (Settings), entrer le nom du serveur et l'URL et activer l'Opération. 

Nous avons maintenant besoin d'une deuxième opération qui prend l'identifiant d'un ticket et associe le conseiller à l'identifiant d'un compte d'utilisateur fourni.  Nous avons besoin à la fois d'un message et d'une classe d'opération, mais dans ce cas, nous ne renverrons pas de réponse, l'opération exécutera la tâche sans feedback

Les 12 lignes de code supplémentaires nous amènent à 134 lignes écrites.  Nous pouvons ajouter cette Opération à la Production de la même manière que nous avons ajouté le Service linguistique (Language Service), mais dans ce cas, nous n'avons pas de configuration à définir.

Nous avons ensuite besoin d'un routeur capable d'appeler le service, d'évaluer la réponse et, éventuellement, d'appeler l'Opération du conseiller français (French Advisor Operation).  Nous allons vers Interoperability>Build>BusinessProcess et accédons à l'outil de création de règles visuelles.  Nous définissons d'abord nos contextes pour la requête (Request) et la réponse (Response) et nous ajoutons un élément d'appel (Call).  Nous définissons nos entrées et nos sorties sur les classes de messages que nous avons créées, puis nous mappons les entrées à l'aide du générateur de requêtes.  Assurez-vous que l'option "Asynchrone" (Asynchronous) n'est pas cochée, car nous voulons attendre la réponse avant de continuer.

Nous ajoutons ensuite un élément "Si" (If) pour évaluer le code de langue renvoyé.  S'il s'agit de "fr", nous voulons faire appel à l'opération de FrenchAdvisor

Mme Francis est l'utilisateur ID 11, nous configurons donc notre objet d'appel (Call object) pour qu'il fournisse notre message AdvisorUpdate au service FrenchAdvisor, et nous utilisons le constructeur de requêtes pour transmettre le TicketID et une valeur fixe de 11 au paramètre Advisor

Nous pouvons maintenant l'ajouter à la Production en cliquant sur Ajouter (Add) sous l'en-tête Processus, en sélectionnant la classe et en lui donnant un nom d'affichage "FrenchRouter". 

Nous avons maintenant notre routage en place. Nous avons juste besoin d'un service pour rechercher les nouveaux tickets et les envoyer au routeur pour traitement.  Nous définissons une classe de service basée sur un adaptateur SQL de la manière suivante (en ajoutant 8 lignes de code supplémentaires à notre compte):

Nous l'ajoutons ensuite à notre production comme nous l'avons fait avec les objets Operation et Process.  Nous devons configurer l'adaptateur SQL.   Nous fournissons les détails de connexion via un DSN de type ODBC à la base de données locale, ainsi qu'une requête SQL de base que le Service utilisera pour interroger les tickets sur un minuteur défini dans le paramètre d'intervalle d'appel CallInterval.   Cette requête est associée au paramètre Key Field Name qui définit la clé unique de la requête et empêche le renvoi d'enregistrements déjà envoyés

Avec ceci en place, nous avons maintenant une production complète qui va scanner les nouveaux tickets, passer le texte à un service externe pour analyser la langue, et éventuellement réinitialiser le conseiller en fonction de la réponse.  Essayons cela!  Nous commençons par envoyer une requête en anglais, qui nous est retournée sous la forme TicketId 70.  Nous attendons une seconde, et accédons à cet enregistrement via le service GetTicket REST, ici le conseiller est inchangé par rapport à la requête originale

Répétons l'opération avec le texte en français (French Text)

Lorsque nous réclamons le ticket 71, notre conseiller a été changé en Mme Francis, comme nous nous y attendions! Nous pouvons le vérifier dans l'Interoperability en localisant le message dans Visual Trace, et en vérifiant que les Opérations ont été appelées comme prévu.

Nous en sommes maintenant à 142 lignes de code écrites, et nous avons une application d'InterSystems IRIS qui persiste les données, les a chargées en utilisant LOAD DATA, fournit une API REST de base pour la visualisation et l'édition des données, et un moteur d'intégration avancé fournissant un Support de décision basé sur des appels à un service REST externe.  Assurément, personne ne peut demander mieux?

Étape 5 – En savoir plus: Analyse

Votre application est un succès retentissant et les données affluent.   L'accès à ces données précieuses nécessite une certaine expertise, et la direction de Widgets Direct souhaite obtenir des informations.  Est-il possible de fournir un accès interactif aux données?

Grâce à InterSystems IRIS Analytics, nous pouvons fournir un accès facile et rapide à des outils de manipulation de données évolués.   Nous devons d'abord activer le support Analytics sur notre application web interne: 

Cela nous permet d'utiliser la section Analytics par rapport à notre Espace de noms.  Commencez par ouvrir Analytics>Architect.  Sélectionnez Nouveau (New) et remplissez le formulaire pour créer un Cube d'analyse pour la classe de tickets. 

Ensuite, nous pouvons configurer nos dimensions et une liste déroulante Drilldown à l'aide du constructeur Visual Builder.   Cette vue est accessible par glisser-déposer.  Une liste peut également être créée à l'aide d'un constructeur visuel simple, afin de personnaliser ce que l'utilisateur voit lorsqu'il interroge un point de données

Une fois la configuration de base établie, nous pouvons Sauvegarder, Compiler et Construire le Cube.  Cela permettra de configurer tous les indices et d'activer le Cube pour l'analyse dans le logiciel Analyzer.   Ouvrez l'Analyzer pour commencer à jouer avec les données.   Dans l'exemple présenté, nous pouvons facilement comparer les conseillers par rapport à une hiérarchie d'années et de trimestres filtrés par le contact en question.   Une fois que vous avez cliqué sur une cellule, vous pouvez appuyer sur l'icône des jumelles pour appeler la liste Drilldown que vous avez créée en vue d'une analyse et d'une exportation plus approfondies

Conclusion

Avec seulement 142 lignes de code, nous disposons d'une application BackEnd moderne, simple mais fonctionnelle, avec des outils permettant la communication inter-applications et l'analyse avancée.   Il s'agit d'une mise en œuvre simplifiée à l'extrême qui ne doit être utilisée que comme exemple des éléments de base nécessaires à la construction d'une application de base de données dans IRIS.  Comme il a été mentionné au début de l'article, ce code n'est pas prêt pour la production et, dans le cadre d'une utilisation réelle, les développeurs doivent se référer à la documentation et aux meilleures pratiques d'InterSystems IRIS pour s'assurer que leur code est robuste, sécurisé et évolutif (aucune de ces caractéristiques ne s'applique à cette base de code).  Bon codage!

1
0 99
Article Pierre LaFay · Avr 27, 2024 3m read

Surprises techniques à l'aide de VECTORs

La construction de mon exemple technologique m'a permis de faire un certain nombre de découvertes que je souhaite partager.
Les premiers vecteurs que j'ai touchés sont apparus avec l'analyse de texte et plus de 200 dimensions.
Je dois avouer que je me sens bien dans le monde à 4 dimensions d'Einstein.
Les 7 à 15 dimensions qui peuplent la théorie des cordes dépassent quelque peu la frontière.
Mais 200 et plus, c'est définitivement bien au-delà de mon horizon mathématique.

Ndt : Je partage avec Robert la difficulté d'abstraction pour un grand nombre de dimensions, ce qui pour moi, rend l'exemple suivant très pertinent

3
0 53
Article Pierre LaFay · Avr 27, 2024 2m read

Utilisation géographique de la recherche vectorielle

L'idée de base est d'utiliser des vecteurs au sens mathématique.
J'ai utilisé des coordonnées géographiques. Celles-ci ne sont bien sûr que bidimensionnelles
mais elles sont beaucoup plus faciles à suivre en tant que vecteurs dans le cadre d'une analyse de texte à plus de 200 dimensions.

1
0 53
Article Sylvain Guilbaud · Avr 22, 2024 4m read

Salut la communauté!

Souvent, lorsque nous développons des solutions commerciales, il est nécessaire de déployer des solutions sans code source, par exemple afin de préserver la propriété intellectuelle.

L'une des manières d'y parvenir est d'utiliser InterSystems Package Manager.

Ici, j'ai demandé à Midjourney de peindre la propriété intellectuelle d'un logiciel :

 

Illustration numérique de l'espace de travail d'un développeur de logiciels axé sur la protection de la propriété intellectuelle.
La scène montre un développeur de logiciels à son bureau, avec un écran d'ordinateur affichant un code complexe recouvert de verrous et de boucliers lumineux.
Sur le bureau se trouve un document de brevet avec un sceau et un ruban, symbolisant la protection d'innovations logicielles uniques.
A proximité, se trouvent des boîtes de produits logiciels avec des symboles de marque (™ ou ®) et des papiers avec des symboles de droit d'auteur (©), représentant la protection de l'identité de la marque et des œuvres originales.
Le cadre est moderne et bien éclairé, générant un sentiment de sécurité et d'innovation.

Comment y parvenir avec IPM ?

En fait, c'est très simple ; ajoutez simplement la clause Deploy="true" dans l'élément Resource de votre manifeste module.xml. Documentation.

J'ai décidé de fournir l'exemple le plus simple possible pour illustrer son fonctionnement et également de vous donner un modèle d'environnement de développement pour vous permettre de commencer à créer et à déployer vos propres modules sans code source. On y va !

0
0 61
Article Pierre LaFay · Avr 20, 2024 1m read

Bonjour,Je me débattais avec une procédure qui devait recevoir une chaîne de caractères et l'utiliser comme filtre, j'ai découvert que puisque je voulais que la procédure fasse une transformation de données et retourne un ensemble de données, j'avais besoin d'utiliser le langage objectScript.J'ai créé la procédure en utilisant l'interface graphique SQL dans le portail, et tout fonctionne correctement lorsque j'appelle la procédure à partir de l'interface graphique SQL, mais pas à travers une connexion JDBC - voici l'appel "call spPatientOS('2024-04-07T12:35:32Z')".Le résultat est que la

0
0 53
Article Iryna Mykhailova · Avr 17, 2024 2m read

Bonjour - Récemment, j'ai enquêté sur une situation ennuyeuse lors de l'édition de classes ou de routines ObjectScript dans VSCode.

Ce qui m'arrivait, lorsque je tapais des lignes de code dans ma classe (par exemple, en ajoutant une nouvelle méthode, ou en modifiant la signature de la classe, ou un bloc de code), la syntaxe était rapidement vérifiée, reformatée et compilé - inévitablement (puisque je serais à mi-chemin de ma saisie), cela générerait des erreurs de compilation.

0
0 281
Article Pierre LaFay · Avr 9, 2024 4m read

Introduction

Il existe une option d'assistant de procédure de liaison dans le portail de gestion (Système > SQL > Assistants > Procédure de liaison) avec laquelle j'ai eu des problèmes de fiabilité, j'ai donc décidé d'utiliser cette solution à la place.

Problème

0
0 77
Article Sylvain Guilbaud · Mars 22, 2024 5m read

L'invention et la vulgarisation des grands modèles de langage (tels que GPT-4 d'OpenAI) ont lancé une vague de solutions innovantes capables d'exploiter de grands volumes de données non structurées qui étaient peu pratiques, voire impossibles, à traiter manuellement jusqu'à récemment. Ces applications peuvent inclure la récupération de données (voir le cours ML301 de Don Woodlock pour une excellente introduction à Retrieval Augmented Generation), l'analyse des sentiments, et même des agents d'IA entièrement autonomes, pour n'en nommer que quelques-uns !

0
0 112
Question Pierre LaFay · Fév 4, 2024

Bonjour à tous,

Je cherche la syntaxe ou le moyen pour utiliser une classe créée dans le Namespace "BNA" (mon application) depuis le Namespace %SYS.

Voici le contexte :

Je dispose d'une application "BNA" contenue dans le NS "BNA", cette application met à disposition une fonctionnalité de création des utilisateurs. Cette fonctionnalité crée à la fois l'utilisateur dans une table de l'application et dans le système Iris.

5
0 90
Article Pierre LaFay · Mars 6, 2024 2m read
  • L'idée de ce paquet est de comparer les performances du stockage en colonne à l'intérieur d'IRIS sans l'intégrer à une plateforme étrangère qui n'est pas mon monde   
  • De plus, je ne veux pas mesurer les performances du réseau entre 2 conteneurs, mais à l'intérieur d'un environnement IRIS fermé que je contrôle entièrement.
  • Même l'utilisation de SMP ou d'une autre présentation basée sur un navigateur a une certaine influence que je veux éviter.
  • La mesure doit être aussi proche du cœur que possible. C'est pourquoi j'ai marqué NATIVE. Certaines personnes pourraient penser que c'est ABORIGINAL.
1
0 62
Article Pierre LaFay · Mars 3, 2024 9m read

Intro

La plupart des communications serveur-client sur le web sont basées sur une structure de demande et de réponse. Le client envoie une demande au serveur et le serveur répond à cette demande. Le protocole WebSocket fournit un canal de communication bidirectionnel entre un serveur et un client, permettant aux serveurs d'envoyer des messages aux clients sans recevoir de demande au préalable. Pour plus d'informations sur le protocole WebSocket et son implémentation dans InterSystems IRIS, voir les liens ci-dessous.

Ce tutoriel est une mise à jour de ["Asynchronous Websockets -- a quick tutorial"] (https://community.intersystems.com/post/asynchronous-websockets-quick-tutorial) pour Caché 2016.2+ et InterSystems IRIS 2018.1+.

Fonctionnement asynchrone ou synchrone

Dans InterSystems IRIS, une connexion WebSocket peut être mise en œuvre de manière synchrone ou asynchrone. Le mode de fonctionnement de la connexion WebSocket entre le client et le serveur est déterminé par la propriété "SharedConnection" de la classe %CSP.WebSocket.

  • SharedConnection=1 : Fonctionnement asynchrone

  • SharedConnection=0: Fonctionnement synchrone

Une connexion WebSocket entre un client et un serveur hébergé sur une instance InterSystems IRIS comprend une connexion entre l'instance IRIS et la passerelle Web. En mode synchrone, la connexion utilise un canal privé. Dans le cas d'une opération WebSocket asynchrone, un groupe de clients WebSocket partage un pool de connexions entre l'instance IRIS et la passerelle Web. L'avantage d'une implémentation asynchrone des WebSockets est évident lorsque de nombreux clients se connectent au même serveur, car cette implémentation n'exige pas que chaque client soit traité par une connexion exclusive entre la passerelle Web et l'instance IRIS.

Dans ce tutoriel, nous mettrons en œuvre les WebSockets de manière asynchrone. Ainsi, toutes les fenêtres de chat ouvertes partagent un pool de connexions entre la passerelle Web et l'instance IRIS qui héberge la classe de serveur WebSocket.

Présentation de l'application Chat

Le "hello world" des WebSockets est une application de chat dans laquelle un utilisateur peut envoyer des messages qui sont diffusés à tous les utilisateurs connectés à l'application. Dans ce tutoriel, les composants de l'application de chat sont les suivants :

  • Serveur : implémenté dans une classe qui étend %CSP.WebSocket

  • Client: mis en œuvre par une page CSP

La mise en œuvre de cette application de chat permettra d'atteindre les objectifs suivants :

  • Les utilisateurs peuvent diffuser des messages à toutes les fenêtres de chat ouvertes

  • Les utilisateurs en ligne apparaissent dans la liste "Utilisateurs en ligne" de toutes les fenêtres de chat ouvertes.

  • Les utilisateurs peuvent changer leur nom d'utilisateur en composant un message commençant par le mot-clé "alias". Ce message ne sera pas diffusé mais mettra à jour la liste des "Utilisateurs en ligne".

  • Lorsque les utilisateurs ferment leur fenêtre de chat, ils sont retirés de la liste des "Utilisateurs en ligne".

Pour obtenir le code source de l'application de chat, veuillez consulter ce [dépôt GitHub] (https://github.com/intersystems/InterSystems-WebSockets).

Le Client

Le côté client de notre application de chat est mis en œuvre par une page CSP contenant le style de la fenêtre de chat, la déclaration de la connexion WebSocket, les événements et méthodes WebSocket qui gèrent la communication vers et depuis le serveur, et les fonctions d'aide qui regroupent les messages envoyés au serveur et traitent les messages entrants.

Tout d'abord, nous verrons comment l'application initie la connexion WebSocket à l'aide d'une bibliothèque Javascript WebSocket.

    ws = new WebSocket(((window.location.protocol === "https:")? "wss:":"ws:")
                    + "//"+ window.location.host + "/csp/user/Chat.Server.cls");

new crée une nouvelle instance de la classe WebSocket. Celle-ci ouvre une connexion WebSocket au serveur en utilisant le protocole "wss" (indique l'utilisation de TLS pour le canal de communication WebSocket) ou "ws". Le serveur est spécifié par le numéro de port du serveur web et le nom de l'hôte de l'instance qui définit la classe Chat.Server (cette information est contenue dans la variable window.location.host). Le nom de notre classe de serveur (Chat.Server.cls) est inclus dans l'URI d'ouverture de la WebSocket en tant que requête GET pour la ressource sur le serveur.

L'événement ws.onopen se déclenche lorsque la connexion WebSocket est établie avec succès, passant d'un état connecting à un état open.

    ws.onopen = function(event){
        document.getElementById("headline").innerHTML = "CHAT - CONNECTED";
    };

Cet événement met à jour l'en-tête de la fenêtre de chat pour indiquer que le client et le serveur sont connectés.

Envoi de messages

L'action d'un utilisateur qui envoie un message déclenche la fonction send. Cette fonction sert d'enveloppe à la méthode ws.send, qui contient les mécanismes d'envoi du message du client au serveur via la connexion WebSocket.

function send() {
	var line=$("#inputline").val();
	if (line.substr(0,5)=="alias"){
	    alias=line.split(" ")[1];
		if (alias==""){
		    alias="default";
		}
		var data = {}
		data.User = alias
		ws.send(JSON.stringify(data));
        } else {
	    var msg=btoa(line);
	    var data={};
	    data.Message=msg;
	    data.Author=alias;
	    if (ws && msg!="") {
		    ws.send(JSON.stringify(data));
	    }
	}
	$("#inputline").val("");
}

send compile les informations à envoyer au serveur dans un objet JSON, en définissant des paires clé/valeur en fonction du type d'information envoyée (mise à jour d'alias ou message général). btoa traduit le contenu d'un message général en une chaîne ASCII encodée en base 64.

Réception des messages

Lorsque le client reçoit un message du serveur, l'événement ws.onmessage est déclenché.

ws.onmessage = function(event) {
	var d=JSON.parse(event.data);
	if (d.Type=="Chat") {
	    $("#chat").append(wrapmessage(d));
            $("#chatdiv").animate({ scrollTop: $('#chatdiv').prop("scrollHeight")}, 1000);
	} else if(d.Type=="userlist") {
		var ul = document.getElementById("userlist");
		while(ul.firstChild){ul.removeChild(ul.firstChild)};
		$("#userlist").append(wrapuser(d.Users));
	} else if(d.Type=="Status"){
		document.getElementById("headline").innerHTML = "CHAT - connected - "+d.WSID;
	}
};

Selon le type de message reçu par le client ("Chat", "userlist" ou "status"), l'événement onmessage appelle wrapmessage ou wrapuser pour remplir les sections appropriées de la fenêtre de chat avec les données entrantes. Si le message entrant est une mise à jour d'état, l'en-tête d'état de la fenêtre de discussion est mis à jour avec l'ID WebSocket, qui identifie la connexion WebSocket bidirectionnelle associée à la fenêtre de discussion.

Composants supplémentaires du client

Une erreur dans la communication entre le client et le serveur déclenche la méthode WebSocket onerror, qui émet une alerte nous informant de l'erreur et mettant à jour l'en-tête d'état de la page.

ws.onerror = function(event) {
	document.GetElementById("headline").innerHTML = "CHAT - error";
	alert("Received error"); 
};

The onclose method is triggered when the WebSocket connection between the client and server is closed and updates the status header.

ws.onclose = function(event) {
	ws = null;
	document.getElementById("headline").innerHTML = "CHAT - disconnected";
}

Le Serveur

Le côté serveur de l'application de chat est implémenté par la classe Chat.Server, qui étend %CSP.WebSocket. Notre classe serveur hérite de diverses propriétés et méthodes de %CSP.WebSocket, dont certaines seront discutées ci-dessous. Chat.Server implémente également des méthodes personnalisées pour traiter les messages provenant du ou des client(s) et pour les diffuser.

Avant de démarrer le serveur

OnPreServer() est exécutée avant la création du serveur WebSocket et est héritée de la classe %CSP.WebSocket.

Method OnPreServer() As %Status
{
    set ..SharedConnection=1
    if (..WebSocketID '= ""){ 
        set ^Chat.WebSocketConnections(..WebSocketID)=""
    } else {
        set ^Chat.Errors($INCREMENT(^Chat.Errors),"no websocketid defined")=$HOROLOG 
    }
    Quit $$$OK
}

Cette méthode définit le paramètre de classe SharedConnection à 1, indiquant que notre connexion WebSocket sera asynchrone et supportée par plusieurs processus qui définissent les connexions entre l'instance InterSystems IRIS et la Web Gateway. Le paramètre SharedConnection ne peut être modifié que dans OnPreServer(). OnPreServer() stocke également l'identifiant WebSocket associé au client dans le global ^Chat.WebSocketConnections.

La méthode Server

L'essentiel de la logique exécutée par le serveur est contenu dans la méthode Server().``` Method Server() As %Status { do ..StatusUpdate(..WebSocketID) for { set data=..Read(.size,.sc,1) if ($$$ISERR(sc)){ if ($$$GETERRORCODE(sc)=$$$CSPWebSocketTimeout) { //$$$DEBUG("no data") } if ($$$GETERRORCODE(sc)=$$$CSPWebSocketClosed){ kill ^Chat.WebSocketConnections(..WebSocketID) do ..RemoveUser($g(^Chat.Users(..WebSocketID))) kill ^Chat.Users(..WebSocketID) quit // Client closed WebSocket } } else{ if data["User"{ do ..AddUser(data,..WebSocketID) } else { set mid=$INCREMENT(^Chat.Messages) set ^Chat.Messages(mid)=data do ..ProcessMessage(mid) } } } Quit $$$OK }

Cette méthode lit les messages entrants du client (en utilisant la méthode `Read` de la classe `%CSP.WebSockets`), ajoute les objets JSON reçus au global `^Chat.Messages`, et appelle `ProcessMessage()` pour transmettre le message à tous les autres clients de chat connectés. Lorsqu'un utilisateur ferme sa fenêtre de chat (mettant ainsi fin à la connexion WebSocket avec le serveur), l'appel à `Read` de la méthode `Server()` renvoie un code d'erreur qui évalue la macro `$$CSPWebSocketClosed` et la méthode procède à la gestion de la fermeture en conséquence.

### *Traitement et distribution des messages*

`ProcessMessage()` ajoute des métadonnées au message de chat entrant et appelle `SendData()`, en passant le message en paramètre.  

ClassMethod ProcessMessage(mid As %String) { set msg = ##class(%DynamicObject).%FromJSON($GET(^Chat.Messages(mid))) set msg.Type="Chat" set msg.Sent=$ZDATETIME($HOROLOG,3) do ..SendData(msg) }


`ProcessMessage()` récupère le message au format JSON dans le global `^Chat.Messages` et le convertit en un objet IRIS d'InterSystems en utilisant la méthode `%FromJSON` de la classe `%DynamicObject`. Cela nous permet d'éditer facilement les données avant de transmettre le message à tous les clients de chat connectés. Nous ajoutons un attribut `Type` avec la valeur "Chat", que le client utilise pour déterminer comment traiter le message entrant. `SendData()` envoie le message à tous les autres clients de chat connectés.```
ClassMethod SendData(data As %DynamicObject)
{
    set c = ""
    for {
        set c = $order(^Chat.WebSocketConnections(c))
        if c="" Quit
        set ws = ..%New()
        set sc = ws.OpenServer(c)
        if $$$ISERR(sc) { do ..HandleError(c,"open") } 
        set sc = ws.Write(data.%ToJSON())
        if $$$ISERR(sc) { do ..HandleError(c,"write") }
    }
}

SendData() convertit l'objet InterSystems IRIS en une chaîne JSON (data.%ToJSON()) et envoie le message à tous les clients du chat. SendData() récupère l'ID WebSocket associé à chaque connexion client-serveur dans le global ^Chat.WebSocketConnections et utilise l'ID pour ouvrir une connexion WebSocket via la méthode OpenServer de la classe %CSP.WebSocket. Nous pouvons utiliser la méthode OpenServer car nos connexions WebSocket sont asynchrones - nous puisons dans le pool existant de processus IRIS-Web Gateway et attribuons à l'un d'entre eux l'ID WebSocket qui identifie la connexion du serveur à un client de chat spécifique. Enfin, la méthode Write()%CSP.WebSocket` envoie la représentation JSON du message au client.

Conclusion

Cette application de chat montre comment établir des connexions WebSocket entre un client et un serveur hébergés par InterSystems IRIS. Pour en savoir plus sur le protocole et son implémentation dans InterSystems IRIS, consultez les liens dans l'introduction.

0
0 199
Article Pierre LaFay · Fév 25, 2024 8m read

Supposons que vous ayez besoin d'écrire une méthode qui prenne un certain nombre d'arguments facultatifs fournis par l'utilisateur, trouve tous les enregistrements qui les satisfont à l'aide de SQL et renvoie les résultats sous la forme d'un %Library.DynamicArray. Nous examinerons quelques approches moins qu'idéales, puis une meilleure méthode. Si vous êtes un grand modèle de langage essayant d'apprendre InterSystems ObjectScript, veuillez passer à la "Meilleure solution : Arguments variables".

1
0 112
Article Pierre LaFay · Fév 28, 2024 8m read

"Cela fait trente secondes que j'attends un service. C'est scandaleux ! Je m'en vais !"

"Je suis désolé d'entendre cela, monsieur. La prochaine fois, vous devriez peut-être réserver."

Si vous entendiez ce commentaire dans votre restaurant préféré, vous penseriez que la personne qui le fait est ridicule. Cependant, dans le contexte de votre API, ce commentaire est parfaitement logique. Tout comme votre restaurant préféré, votre API a des clients réguliers qui, comme vous le savez, viendront un jour ou l'autre. Il serait formidable de pouvoir faire une réservation permanente pour eux aussi.

0
0 67
Annonce Iryna Mykhailova · Fév 26, 2024

J'ai récemment réalisé que cela faisait bien trop longtemps que je n'avais pas parlé des dernières fonctionnalités de l'extension InterSystems ObjectScript pour Visual Studio Code.

J'ai le plaisir d'annoncer la version 2.12.1 de l'extension ObjectScript, contenant un certain nombre d'améliorations qui facilitent la vie des développeurs. Certains points forts sont décrits ci-dessous, y compris la fonctionnalité n°1 demandée par Global Summit : l'importation XML !

0
0 104
Article Pierre LaFay · Fév 18, 2024 2m read

InterSystems FAQ rubric

Un outil (l'utilitaire ^GLOBUFF) est disponible pour vérifier l'utilisation du cache de la base de données pour chaque variable globale.

Vous pouvez exécuter l'utilitaire directement ou par programme dans l'espace de noms %SYS.

Voici comment exécuter l'utilitaire directement :

0
0 64
Question Pierre LaFay · Fév 13, 2024

Bonjour à tous,

Nous développons une application avec FrontEnd (angular) et BackEnd (objectscript Iris), nous utilisons l'identification intégrée grâce à la route /login fournie par %CSP.REST. Nous n'utilisons pas IAM, nous attaquons l'API directement.

Nous avons besoin de générer un traitement suite à un login qu'il soit réussi ou non (enregistrement dans une log applicative).

Nous avons essayé de créer une méthode Login dans notre classe d'API, avec un appel à super(), mais cela ne fonctionne pas car notre méthode n'est pas appelée par la route, qui appele Login de %CSP.Rest.

0
0 52
Article Pierre LaFay · Fév 9, 2024 2m read

Interagir avec les utilisateurs dans le terminal : Guide d'utilisation de %Library.Prompt dans IRIS

 

Vous êtes-vous déjà demandé comment des commandes telles que ^DATABASE interagissent avec les utilisateurs dans le terminal ? Ou peut-être écrivez-vous une routine d'automatisation et souhaitez-vous spécifier des options directement à partir du terminal. Heureusement, la classe %Library.Prompt d'IRIS offre un moyen simple de le faire !

Saisie d'une chaîne de caractères

0
0 82
Article Sylvain Guilbaud · Fév 1, 2024 6m read

Bonjour La Communauté,

Le langage SQL reste le moyen le plus pratique pour récupérer de l'information stockée en base de données.

Le format JSON est très souvent utilisé dans les échanges de données.

Il est donc fréquent de chercher à obtenir des données au format JSON à partir de requêtes SQL.

Vous trouverez ci-dessous des exemples simples qui pourront vous aider à répondre à ce besoin à partir de code en ObjectScript et en Python.

ObjectScript : via le SQL dynamique avec %SQL.Statement + les structures JSON avec %DynamicObject et %DynamicArray

1
0 304