Création d’un framework Java-REST



On voudrait faire simple : on utiliserait JAX-RS afin de créer son API Restful. Ça ne sera pas notre cas ici et nous construirons notre API à partir de Java et rien que de java. J’ai envie de dire sans sucre (ou plutot librairies) ajoutées. Et ce afin de ne pas polluer notre framework de choses pas toujours utiles. Du moins pas utiles pour tout le monde.

RESTFul, ça veut dire quoi ?

Les services Web RESTful ont été conçus pour être particulièrement efficace sur le Web. Ils se sont démocratisés suite à la lourdeur (en terme de déploiement et performance) d’utilisation des services SOAP. Le « Representational State Transfer » (REST) est un style d’architecture logicielle qui spécifie des contraintes. l’uniformité de l’interface en est une. Point très important : dans les architectures REST, les données et les actions sont considérées comme des ressources et sont accessibles via les identificateurs de ressource uniformes (URI). En fait il s’agit de liens sur le Web.

Ces ressources sont utilisées en utilisant un ensemble d’opérations simples et bien définies (l’API). L’architecture REST est de plus une architecture client / serveur conçu pour utiliser un protocole de communication sans état, généralement HTTP. Dans le style d’architecture REST, les clients et les serveurs échangent des représentations de ressources en utilisant une interface et un protocole standardisé.

Voilà pour la définition globale d’une architecture RESTFul. mais qu’est-ce que ça veut dire exactement ?

Les services REST s’utilisent via des appels d’URL/URI au travers d’HTTP.

Mais si cela parait plus simple que l’utilisation d’un catalogue de services (suis-moi du regard WDSL), celà nécessite une rigueur de définition et d’utilisation sans faille. Sans quoi l’anarchie guette !

Caractéristiques des services RESTFul

C’est donc noté pas d’annuaire qui référence les services et pas de contrat d’interfaces publiées. Ok. Des URI combinées à un protocole HTTP pour appeller les services. Encore Ok. Mais comment tout cela se combine-t-il ? et bien tout simplement, trop peut être car je le disais cette simplicité doit s’accompagner d’une certaine rigueur. Dés lors que l’on va créer son API la première tâche sera de mettre à plat sa taxonomie.

Par exemple: Imaginez une API qui gère des voitures d’un garage.

J’ai besoin de créer, utiliser, réparer, retirer, etc. des voitures. Mais j’ai besoin aussi dé récupérer la liste des voitures en cours de réparation, celles présentes dans le parking, voir les caractéristiques détaillées, etc.

J’ai donc besoin d’une API en mode CRUD (Creation/Read/Update/Delete) ! voilà à quoi ça pourrait ressembler à la sauce RESTFul :

  • GET
    • http://monserver/voiture/1 : affichera les détails sur la voiture qui a pour identifiant 1
    • http://monserver/voitures : affichera la liste des voitures (on pourrait aussi avoir GET http://monserver/parking, bref c’est votre choix)
    • http://monserver/rechercher/voitures?marque=peugeot : lance une recherche de voiture de marque Peugeot
  • POST
    • http://monserver/voiture : Ajoute une nouvelle voiture (les caractéristiques ne sont pas dans l’URL cat nous sommes en POST)
  • PUT
    • http://monserver/voiture/1 : modifie des informations de la voiture qui a pour identifiant 1
  • DELETE
    • http://monserver/voiture/1 : retire la voiture qui a pour identifiant 1 du parking

 

Pour synthétiser, on retrouve l’utilisation du protocole HTTP (via ses methodes get, post, put, delete) pour traduire les actions CRUD. Sachez d’ailleurs que les codes retours HTTP sont aussi normalisés et que vous devrez alors les respecter ! On aura des codes retours du type : 200 (OK), 400 (Bad Request), 404 (Not Found), etc.

L’implémentation RESTFul dans Joy

J’aime la simplicité et pour celà les normes respectées au pied de la lettre ne sont pas nécessairement une bonne approche. J’ai donc décidé de faire des petites coupes dans ce que j’ai décrit ci-dessous afin de me simplifier la vie et surtout de gagner en efficacité. A titre d’exemple, pour le protocole http, je n’utiliserai pas la méthode PUT, la méthode POST gérera un mode « upsert » c’est a dire création et/ou mise à jour.

Pour celà je créé donc ma servlet (ou plutot mon Filtre) qui récupérera les appels d’API. Selon la méthode (protocole HTPP appelée) je redirigerai vers une interface spécialisée bien sur afin d’exécuter le code qui va bien.

Classe com.joy.api.filter.FilterAPI :

public class FilterAPI extends FilterCommon
{
    protected void process(JoyState state) {

        try {
            // Get call informations
            ApiConfigEntry myRestCall = new ApiConfigEntry(state.getAPIRequest().getMainAction(), state.getRestConfiguration());
            String resultREST = "";
            
            state.getLog().log(Level.INFO, "REST action requested");
            ActionTypeREST actionRestObject = (ActionTypeREST) Class.forName(myRestCall.getClassName()).newInstance();
            actionRestObject.init(state);
            
            switch (state.getAPIRequest().getHttpMethod()) {
                case "DELETE": // Delete
                    JoyJsonPOSTReturn retDELETE = new JoyJsonPOSTReturn();
                    retDELETE.setUpdateType(delete);
                    resultREST = actionRestObject.restDelete(retDELETE); 
                    state.getCurrentResponse().setStatus(SC_OK);
                    if (resultREST.equalsIgnoreCase(RESTFUL_NOT_FOUND))
                        state.getCurrentResponse().setStatus(SC_NOT_FOUND);
                    resultREST = retDELETE.getJsonReturn().toString();
                    break;
                    
                case "POST": // Create or update
                    JoyJsonPOSTReturn retPOST = new JoyJsonPOSTReturn();
                    resultREST = actionRestObject.restPost(retPOST); 
                    retPOST.setUpdateType(upsert);
                    switch (resultREST) {
                        case RESTFUL_NOT_FOUND: state.getCurrentResponse().setStatus(SC_NOT_FOUND); break;
                        case RESTFUL_ALREADY_EXIST: state.getCurrentResponse().setStatus(SC_CONFLICT); break;
                        case RESTFUL_NO_CONTENT: state.getCurrentResponse().setStatus(SC_NO_CONTENT);break;
                        case RESTFUL_OK:
                        default: state.getCurrentResponse().setStatus(SC_OK);
                    }
                    resultREST = retPOST.getJsonReturn().toString();
                    break;
                    
                case "GET": // Get-read default
                default:
                    resultREST = actionRestObject.restGet();
                    state.getCurrentResponse().setStatus(SC_OK);
                    if (resultREST.equalsIgnoreCase(RESTFUL_NOT_FOUND))
                        state.getCurrentResponse().setStatus(SC_NOT_FOUND);
            }

            state.getCurrentResponse().setContentType (myRestCall.getMime());
            if (myRestCall.getMime().equalsIgnoreCase("unknown/unknown")) {
                // Force a file download
                state.getCurrentResponse().setHeader ("Content-Disposition", "attachment; filename=\"\"");
                ServletOutputStream outs = state.getCurrentResponse().getOutputStream();
                outs.print(resultREST);
                outs.flush();
                outs.close();
                
            } else {
                // Return classic flow (in json)
                PrintWriter out = state.getCurrentResponse().getWriter();
                out.print( resultREST );
                out.close();
            }
            
        } catch (IOException | ClassNotFoundException | IllegalAccessException | InstantiationException ex) {
            getLog().log(Level.SEVERE, "FilterAPI.process> IOException={0}", ex);
        }
        
        ...
    }
}

Vous remarquerez que c’est à ce niveau que l’on gérera les codes retours http selon les erreurs standards rencontrées (RESTFUL_NOT_FOUND, SC_NOT_FOUND, etc.).

Utiliser le framework Joy

Afin de créer un élément d’API (comme pour les voitures de notre exemple), il suffit de créer une classe java qui dérive de ActionTypeREST:

package com.dgm.api.src;

import com.joy.api.ActionTypeREST;
import com.joy.api.JoyApiRequestParameter;
import com.joy.api.beans.JoyJsonPOSTReturn;
import static com.joy.api.beans.JoyJsonPOSTReturn.JoyEnumPOSTUpSertCodes.insert;
import static com.joy.api.beans.JoyJsonPOSTReturn.JoyEnumPOSTUpSertCodes.update;
import com.joy.bo.BOEntityReadWrite;
import com.joy.bo.IEntity;
import java.util.logging.Level;

/**
 *
 * @author Benoit Cayla ([email protected])
 */
public class RESTSrcCommon extends ActionTypeREST {
    
    public boolean beforeDelete(JoyJsonPOSTReturn retDELETE) {
        // TO OVERRIDE WITH SPECIFICS ...
        return true; // return false to prevent delete
    }
    
    public boolean afterInsert(JoyJsonPOSTReturn retD) {
        // TO OVERRIDE WITH SPECIFICS ...
        return true; // return false to prevent delete
    }
    
    /**
     * Delete an row in this.table table
     * Filter with the ID if given in the URL
     * DELETE http://[...]/api/termtype?[TableFieldKey]=X
     * @return json content
     */
    @Override
    public String restDelete(JoyJsonPOSTReturn retDELETE) {
        ...
        return this.getStatusOk();
    }

    /**
     * Override this function to check if exists
     * @param retPOST
     * @return 
     */
    public boolean preventInsert(JoyJsonPOSTReturn retPOST) {
        // TO OVERRIDE WITH SPECIFICS ...
        return false;
    }
    
    /**
     * Override this function to update fields with specifics parameters/fields values
     * otherwise match the http parameter with the table fields (with the same names)
     * @param Entity 
     */
    protected void matchFieldsAndhttpParams(IEntity Entity) {
        // TO OVERRIDE WITH SPECIFICS afterwards
    }
    
    /**
     * Add or update a new record (upsert mode)
     * POST http://[...]/api/termtype
     *      * execute an insert if param[TableFieldKey] == 0
     *      * execute an update else
     * fields in the parameters POST request
     * @return 
     */
    @Override
    public String restPost(JoyJsonPOSTReturn retPOST) {
        ...
        return this.getStatusOk();
    }

    /**
     * Return the this.Table table content
     * Filter with the ID if given in the URL
     * GET http://[...]/api/termtype (full table)
     * GET http://[...]/api/termtype?[TableFieldKey]=X (only record with id)
     * @return json content
     */
    @Override
    public String restGet() {
        ...
    
}

Vous ne remarquez rien ?

J’ai aussi ajouté des user-exit afin de pouvoir spécifier du code avant et après certaines actions de mise à jour (exemple : public boolean preventInsert(JoyJsonPOSTReturn retPOST) {}). L’idée ici aussi est de faciliter la lecture du code en ajoutant par exemple des controles avant et/ou après mise à jour. Petit détail certes, mais très pratique pour la suite 😉

Téléchargez Joy



A propos de Benoit Cayla

En plus d'être un spécialiste des données et processus d'entreprise, j'aime passer le plus clair de mon temps à l'extérieur, à jardiner par exemple ou simplement à aller dans le bois avec mes enfants. J'adore aussi le VTT que je pratique chaque semaine avec mes amis. Quand je dois rester à l'intérieur, j'aime améliorer ma maison. Bricoler me permet ainsi de libérer ma créativité d'ingénieur. Et, si je n'ai rien à réparer ou créer, je passe mon temps libre à explorer les dernières avancées technologiques dans le monde de l'IA et du RPA.

Laissez un commentaire

Votre adresse de messagerie ne sera pas publiée.

15 + 8 =

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.