… ou comment appeler des web services en quasi-temps réel et présenter nos données
En ces temps d’ultra-connectivité, de mise à disposition des données en temps réel, de consommation instantanée de l’information, il s’avère souvent qu’un des casse-têtes des DBA reste la présentation de ces données.
Prenons l’exemple d’un site permettant d’afficher la disponibilité d’un produit. N’y a-t-il rien de plus rageant lors de la validation d’une commande de recevoir un mail, nous indiquant, bien cordialement, qu’un remboursement va avoir lieu du fait de l’indisponibilité du produit ?
Pourquoi ? Tout simplement à cause de la fraîcheur des données. Beaucoup de systèmes sont des évolutions d’évolutions évoluant sur la base d’évolution.
Je ne pense pas (et ne le prétends pas) avoir l’unique solution, mais voici une des solutions envisageables : la Vue Matérialisée !
Vous allez me dire, rien d’innovant, rien de compliqué… Non, c’est sûr mais voyons-en l’implémentation :
Dans un premier temps, nous allons créer une table de test :
CREATE TABLE Test_PGR (ID1 NUMBER(50), ID2 VARCHAR2(50), ID3 NUMBER(50), C01 NUMBER(30), C02 NUMBER(30), C03 NUMBER(30), C04 NUMBER(30), C05 NUMBER(30), C06 NUMBER(30));
Ajoutons maintenant une clé primaire :
ALTER TABLE Test_PGR ADD CONSTRAINT Test_PGR_PK PRIMARY KEY (ID1, ID2, ID3);
Afin d’être efficace, nous allons devoir utiliser un refresh de notre vue matérialisée en mode FAST.
Pour rappel, ce mode est applicable sur des MV simples, sous certaines conditions, et permet de rafraîchir les MV de façon synchrone et incrémentale.
Ainsi, il nous faut donc créer un log de vue matérialisée sur notre table :
CREATE MATERIALIZED VIEW LOG ON Test_PGR;
Maintenant que les conditions sont respectées pour le FAST refresh, nous allons pouvoir créer notre MV :
CREATE MATERIALIZED VIEW Test_MV BUILD IMMEDIATE REFRESH FAST ON COMMIT DISABLE QUERY REWRITE AS SELECT ID1, ID2, ID3, C01, C02, C03, C04 FROM Test_PGR;
Et voilà !
Mais pourquoi toutes ces clauses ?
- BUILD IMMEDIATE : construction de la MV à la création
- REFRESH FAST : mise en place du refresh incrémentiel
- ON COMMIT : sur commit effectué sur la table de base
- DISABLE QUERY REWRITE : Bridage d’Oracle pour ne pas avoir de régression. Si l’option n’est pas désactivée, Oracle risque de décider de passer par la vue matérialisée lors de SELECT pensant que l’action serait plus efficace.
Nous disposons maintenant de tout pour avoir nos données au fil de l’eau. En cas d’INSERT ou d’UPDATE dans la table de base (Test_PGR), la modification sera enregistrée dans le MVLog et suite au COMMIT les données seront présentes dans la MV.
Ainsi, si nous souhaitons prendre connaissance de ces changements toujours au fil de l’eau, nous allons créer un trigger basé sur les mouvements de la MV qui déclenchera nos actions.
Pour que l’exemple soit parlant, j’ai décidé d’écrire mes données dans un fichier sur le filesystem de mon serveur de base de données Oracle. Bien entendu, pour ce faire, l’utilisateur possédant le trigger doit avoir les droits d’écriture sur l’objet directory.
CREATE OR REPLACE TRIGGER MV_TRIG AFTER INSERT OR UPDATE ON Test_MV REFERENCING NEW AS NEW FOR EACH ROW DECLARE fileHandler UTL_FILE.FILE_TYPE; v_filename VARCHAR2(50); v_buffer VARCHAR2(32767); BEGIN v_filename := 'TEST_' || TO_CHAR(sysdate,'YYYYMMDDHH24MISS') || '.lst'; fileHandler := UTL_FILE.FOPEN('DATA_OUT', v_filename, 'A'); v_buffer := :new.ID1 ||';' || :new.ID2 ||';' || :new.ID3 ||';' || :new.C01 ||';' || :new.C02 ||';' || :new.C03 ||';' || :new.C04 || '\n'; UTL_FILE.PUTF(fileHandler, v_buffer); UTL_FILE.FCLOSE(fileHandler); EXCEPTION WHEN utl_file.invalid_path THEN raise_application_error(-20000, 'ERROR: Mauvais chemin pour le fichier de sortie.'); END;
Que se passe-t-il lors d’un INSERT dans la table de base ?
Tout simplement, Oracle enregistre les données dans les datafiles ainsi que dans le log de vue matérialisée. Une fois le COMMIT consommé par la base, Oracle rafraîchit les données dans la MV tout en déclenchant notre trigger.
A son tour, le trigger crée un fichier contenant les différentes lignes, pour chaque seconde du fait du nommage dans le fichier.
1;Produit1;1;10;10;10;10 2;Produit2;1;15;20;25;5 1;Produit2;2;10;42;12;4
Pour ce qui est de la consommation de nos fichiers et donc de l’appel à notre Web Service, rien de plus simple :
while IFS=\; read id1 id2 id3 col1 col2 col3 col4 do curl \ --header "Content-type: application/json" \ --resquest POST \ --data '{"Site":"$id1","Produit":"$id2","Couleur":"$id3","dispo":"$col1","restant":"$col2","Pct1":"$col3","Pct2":"$col4"}' \ http://monAS/stock/rest done < TEST_20160324133318.lst
Et voilà, il est éventuellement possible d’englober cette consommation dans un script scannant le dossier d’entrées, de variabiliser le nom du fichier et ainsi d’appeler notre Web Service au fil de l’eau.
Comme je l’indiquais précédemment, ce n’est peut-être pas la meilleure solution (vous pouvez d’ailleurs en discuter dans les commentaires), mais dans l’absolu, l’utilisation de ces fonctionnalités Oracle est simple à mettre en œuvre et permet d’avoir un impact moindre qu’en utilisant un trigger directement sur la table de base.
Sur ce, bonne continuation !