Article
· Juil 17, 2023 11m de lecture

Création d'un service REST dans IRIS

Le besoin de créer des services REST permettant d'accéder aux informations présentes dans IRIS / HealthConnect est l'un des besoins les plus courants de nos clients. L'avantage de ces services REST est la possibilité de développer des interfaces utilisateurs personnalisées avec les technologies les plus récentes en profitant de la fiabilité et de la performance d'IRIS dans le back-end.

Dans l'article d'aujourd'hui, nous allons créer pas à pas un service web qui nous permettra à la fois de stocker des données dans notre base de données et de les consulter ultérieurement. De plus, nous allons le faire en configurant une production qui nous permettra de contrôler le flux de messages que nous recevons à tout moment et de contrôler ainsi son bon fonctionnement.

Avant de commencer, sachez simplement que vous avez tout le code disponible sur Open Exchange pour que vous puissiez le répliquer autant de fois que nécessaire, et que le projet est configuré dans Docker, en utilisant la communauté IRIS afin que vous n'ayez rien d'autre à faire que de le déployer.

Bien! Commençons donc!

Préparation de l'environnement

Avant de commencer la configuration du service REST, nous devons préparer notre environnement de développement, savoir quelles informations nous allons recevoir et ce que nous allons en faire. Pour cet exemple, nous avons décidé de recevoir des données de la personne dans le format JSON suivant :

{
    "PersonId": 1,
    "Name": "Irene",
    "LastName": "Dukas",
    "Sex": "Female",
    "Dob": "01/04/1975"
}

Comme l'un des objectifs est de stocker les informations que nous recevons, nous allons créer une classe Objectscript qui nous permettra d'enregistrer les informations dans notre IRIS. Comme vous pouvez le constater, les données sont assez simples, donc la classe n'aura pas beaucoup de complications :

Class WSTEST.Object.Person Extends %Persistent
{

/// ID of the person
Property PersonId As %Integer;
/// Name of the person
Property Name As %String;
/// Lastname of the person
Property LastName As %String;
/// Sex of the person
Property Sex As %String;
/// DOB of the person
Property Dob As %String;
Index PersonIDX On PersonId [ PrimaryKey ];
}

Parfait, nous avons déjà défini notre classe et nous pouvons commencer à nous en servir.

Création de notre point de terminaison

Maintenant que nous avons défini la classe de données avec laquelle nous allons travailler, il est temps de créer notre classe Objectscript qui fonctionnera comme un point de terminaison qui sera appelé depuis notre front-end. Voyons étape par étape la classe d'exemple que nous avons dans notre projet :

Class WSTEST.Endpoint Extends %CSP.REST
{

Parameter HandleCorsRequest = 0;
XData UrlMap [ XMLNamespace = "https://www.intersystems.com/urlmap" ]
{
<Routes>
    <Route Url="/testGet/:pid" Method="GET" Call="TestGet" />
    <Route Url="/testPost" Method="POST" Call="TestPost" />
</Routes>
}

ClassMethod OnHandleCorsRequest(url As %String) As %Status
{
    set url = %request.GetCgiEnv("HTTP_REFERER")
    set origin = $p(url,"/",1,3) // origin = "http(s)://origin.com:port"
    // here you can check specific origins
    // otherway, it will allow all origins (useful while developing only)
    do %response.SetHeader("Access-Control-Allow-Credentials","true")
    do %response.SetHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS")
    do %response.SetHeader("Access-Control-Allow-Origin",origin)
    do %response.SetHeader("Access-Control-Allow-Headers","Access-Control-Allow-Origin, Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control")
    quit $$$OK
}
// Class method to retrieve the data of a person filtered by PersonId
ClassMethod TestGet(pid As %Integer) As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        // Creation of BS instance
        set status = ##class(Ens.Director).CreateBusinessService("WSTEST.BS.PersonSearchBS", .instance)

        // Invocation of BS with pid parameter
        set status = instance.OnProcessInput(pid, .response)
        if $ISOBJECT(response) {
            // Sending person data to client in JSON format
            Do ##class(%REST.Impl).%WriteResponse(response.%JSONExport())
        }
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        Do ##class(%REST.Impl).%WriteResponse(ex.DisplayString())
        return {"errormessage": "Client error"}
    }
    Quit $$$OK
}
// Class method to receive person data to persist in our database
ClassMethod TestPost() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        // Reading the body of the http call with the person data
        set bodyJson = %request.Content.Read()
        
        // Creation of BS instance
        set status = ##class(Ens.Director).CreateBusinessService("WSTEST.BS.PersonSaveBS", .instance)
        #dim response as %DynamicObject
        // Invocation of BS with person data
        set status = instance.OnProcessInput(bodyJson, .response)
        
        if $ISOBJECT(response) {
            // Returning to the client the person object in JSON format after save it
            Do ##class(%REST.Impl).%WriteResponse(response.%JSONExport())
        }
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        Do ##class(%REST.Impl).%WriteResponse(ex.DisplayString())
        return {"errormessage": "Client error"}
    }
    Quit $$$OK
}

}

Ne vous inquiétez pas si cela vous semble impossible à comprendre, voyons les parties les plus pertinentes de notre cours :

Déclaration de classe :

Class WSTEST.Endpoint Extends %CSP.REST

Comme vous pouvez le voir, notre classe WSTEST.Endpoint élargit %CSP.REST, ce qui est nécessaire pour pouvoir utiliser la classe en tant que point de terminaison.

Définition des chemins :

XData UrlMap [ XMLNamespace = "https://www.intersystems.com/urlmap" ]
{
<Routes>
    <Route Url="/testGet/:pid" Method="GET" Call="TestGet" />
    <Route Url="/testPost" Method="POST" Call="TestPost" />
</Routes>
}

Dans cet extrait de code, nous déclarons les chemins qui peuvent être appelés à partir de notre front-end.

Comme vous voyez, nous avons deux chemins déclarés, le premier sera un appel GET dans lequel nous recevrons le paramètre pid que nous utiliserons pour rechercher des personnes par leur identifiant et qui sera géré par la méthode de classe TestGet. Le second appel sera de type POST, dans lequel nous recevrons les informations sur la personne que nous devons enregistrer dans notre base de données et qui seront traitées par la méthode de classe TestPost.

Examinons les deux méthodes :

Récupération des données de la personne :

ClassMethod TestGet(pid As %Integer) As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        // Creation of BS instance
        set status = ##class(Ens.Director).CreateBusinessService("WSTEST.BS.PersonSearchBS", .instance)

        // Invocation of BS with pid parameter
        set status = instance.OnProcessInput(pid, .response)
        if $ISOBJECT(response) {
            // Sending person data to client in JSON format
            Do ##class(%REST.Impl).%WriteResponse(response.%JSONExport())
        }
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        Do ##class(%REST.Impl).%WriteResponse(ex.DisplayString())
        return {"errormessage": "Client error"}
    }
    Quit $$$OK
}

Dans cette méthode, vous pouvez voir comment nous avons déclaré dans notre méthode de classe la réception de l'attribut pid que nous utiliserons dans la recherche ultérieure. Bien que nous aurions pu effectuer la recherche directement à partir de cette classe, nous avons décidé de le faire à l'intérieur d'une production pour pouvoir contrôler chacune des opérations, c'est pourquoi nous créons une instance du service métier Business Service WSTEST.BS.PersonSearchBS à laquelle nous appelons plus tard sa méthode OnProcessInput avec le pid reçu. La réponse que nous recevrons sera de type WSTEST.Object.PersonSearchResponse que nous transformerons en JSON avant de l'envoyer au requérant.

Conservation des données de la personne :

ClassMethod TestPost() As %Status
{
    Try {
        Do ##class(%REST.Impl).%SetContentType("application/json")
        If '##class(%REST.Impl).%CheckAccepts("application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        // Reading the body of the http call with the person data
        set bodyJson = %request.Content.Read()
        
        // Creation of BS instance
        set status = ##class(Ens.Director).CreateBusinessService("WSTEST.BS.PersonSaveBS", .instance)
        #dim response as %DynamicObject
        // Invocation of BS with person data
        set status = instance.OnProcessInput(bodyJson, .response)
        
        if $ISOBJECT(response) {
            // Returning to the client the person object in JSON format after save it
            Do ##class(%REST.Impl).%WriteResponse(response.%JSONExport())
        }
        
    } Catch (ex) {
        Do ##class(%REST.Impl).%SetStatusCode("400")
        Do ##class(%REST.Impl).%WriteResponse(ex.DisplayString())
        return {"errormessage": "Client error"}
    }
    Quit $$$OK
}

Comme dans le cas précédent, nous aurions pu sauvegarder notre objet personne directement à partir de cette classe, mais nous avons décidé de le faire à partir d'une opération métier "Business Operation" qui sera appelée à partir du service métier "Business Service" WSTEST.BS.PersonSaveBS.

Comme vous pouvez le voir dans le code, nous récupérons les informations envoyées par le client dans l'appel POST en lisant le flux figurant dans %request.Content. La chaîne obtenue sera ce que nous transmettrons au Business Service..

 

Publication de notre point de terminaison

Pour ne pas allonger cet article, nous allons ignorer les explications relatives à la production, vous pouvez consulter le code directement dans le projet OpenExchange. La production que nous avons configurée est la suivante :

Nous avons deux services commerciaux déclarés, l'un pour recevoir les demandes de recherche et l'autre pour les demandes de stockage des données de la personne. Chacun d'entre eux invoquera une opération métier Business Operation appropriée.

Très bien, révisons ce que nous avons configuré dans notre projet :

  • 1 classe de point de terminaison qui recevra les requêtes des clients (WSTest.Endpoint).
  • 2 services métier "Business Services" qui seront appelés à partir de notre classe de point de terminaison (WSTest.BS.PersonSaveBS **et **WSTest.BS.PersonSearchBS).
  • 2 opérations métier "Business Operations" chargées d'effectuer la recherche et l'enregistrement des données (WSTest.BS.PersonSaveBS **et **WSTest.BS.PersonSearchBS).
  • 4 classes pour envoyer et recevoir des données au sein de la production qui s'étendent de Ens.Request et Ens.Response (WSTest.Object.PersonSearchRequestWSTest.Object.PersonSaveRequestWSTest.Object.PersonSearchResponse WSTest.Object.PersonSaveResponse).

Il ne nous reste plus qu'une dernière étape pour mettre en service notre service web, c'est sa publication. Pour ce faire, nous allons accéder à l'option du portail de gestion Administration du système --> Sécurité -> Applications -> Applications Web

Nous verrons une liste de toutes les applications Web configurées dans notre instance :

Créons notre application web :

Examinons les points à configurer :

  • Nom : Nous allons définir la route à utiliser pour faire les invocations à notre service, pour notre exemple ce sera /csp/rest/wstest
  • Espace de noms : l'espace de noms sur lequel le service web travaillera, dans ce cas nous avons créé WSTEST dans lequel nous avons configuré notre production et nos classes.
  • Activer l'application : activer le service web pour qu'il puisse recevoir des appels.
  • Activer - REST : En sélectionnant REST, nous indiquons que ce service web est configuré pour recevoir des appels REST. En sélectionnant REST, nous devons définir la classe de distribution qui sera notre classe de terminaison WSTEST.Endpoint.
  • Méthodes d'authentification autorisées : la configuration de l'authentification de l'utilisateur qui fait l'appel au service web. Dans ce cas, nous avons défini que l'authentification se fait par mot de passe, donc dans notre Postman nous configurerons le mode d'autorisation de base dans lequel nous indiquerons le nom d'utilisateur et le mot de passe. Nous pouvons définir l'utilisation de l'authentification JWT qui est très utile car elle n'expose pas les données de l'utilisateur et le mot de passe dans les appels REST, si vous êtes intéressé à plonger dans JWT, vous pouvez consulter [cet article] (https://community.intersystems.com/post/creating-rest-api-jwt-authentica...).

Une fois que nous avons fini de configurer notre application web, nous pouvons lancer quelques tests au moyen de Postman et en important le fichier WSTest.postman_collection.json présent dans le projet.

Test de notre point de terminaison

Une fois que tout est configuré dans notre projet et que la production a démarré, nous pouvons lancer quelques tests sur notre service web. Nous avons configuré superuser en tant qu'utilisateur requérant, de sorte que nous n'aurons pas de problèmes pour sauvegarder et récupérer des données. Dans le cas de l'utilisation d'un autre utilisateur, nous devons nous assurer qu'il a les rôles nécessaires assignés ou que nous les assignons dans l'onglet Rôles de l'application de la définition de notre application web.

Commençons par enregistrer une personne dans notre base de données :

Nous avons obtenu un 200, il semble donc que tout se soit bien passé, vérifions le message en production :

Tout s'est bien passé, le message avec le JSON a été correctement reçu dans BS et a été enregistré avec succès dans BO.

Essayons maintenant de récupérer les données de notre cher Alexios Kommenos :

Bingo ! Voici notre Alexios, avec toutes les informations le concernant. Vérifions le message en production :

Très bien, tout fonctionne comme il faut. Comme vous l'avez vu, il est très facile de créer un service web REST dans IRIS, vous pouvez utiliser ce projet comme base pour vos futurs développements et si vous avez des questions, n'hésitez pas à laisser un commentaire.

Discussion (0)2
Connectez-vous ou inscrivez-vous pour continuer