Romain JOURDIER, Leader Technique, a assisté à DevoxxFR 2013 et nous a rapporté une vision à 360° de ce que la programmation fonctionnelle va apporter et comment elle s’intègre dans l’écosystème Java: maintenant et dans le futur.

1. Des fonctions d’ordre supérieur

Pour un programmeur Java, la programmation fonctionnelle peut sembler déroutante. La vue d’un programme écrit dans un langage fonctionnel n’aide généralement pas à sa compréhension pour un « non-initié ». Pourtant comme souvent, seul le premier pas est le plus difficile, et le gain que l’on peut en tirer peut valoir le coup d’œil.

1.1. Un exemple

Là où un programme Java décrit comment arriver à la solution via une succession d’états, un programme fonctionnel va décrire ce qu’est la solution (quoi plutôt que comment). Par exemple, pour récupérer les noms des personnes majeures à partir d’une liste de personnes, voici une version java et une version fonctionnelle :

Version Java :

public String getNomsPersonnesMajeures(List<Personne> personnes) {
  String result = "";
  boolean isFirstResult = true;
 
  for (Personne p : personnes) {
    if (p.getAge() >= 18) {       // Récupération des personnes majeures
      String nom = p.getNom();    // Récupération des noms
      if (nom.isNotBlank()) {     // Retrait des noms vides
 
        if (isFirstResult) {
          isFirstResult = false;
 
        } else {
          result += ", ";         // Concaténation
        }
 
        result += nom;            // Concaténation
      }
    }
  }
 
  return result;
}

Version fonctionnelle (pseudo-code) :

fonction getNomsPersonnesMajeures(personnes: List<Personne>): String = {
    pour les personnes qui vérifient age >= 18
      je veux leurs noms
      qui ne sont pas vides
      et je les concatène
}

Cette dernière version, bien plus lisible dès le premier coup d’œil, se décompose ainsi :

  • Filtrage des personnes : seulement les personnes majeures
  • Transformation des personnes : les personnes sont réduites à leurs noms
  • Filtrage des noms : seulement les noms non vides
  • Agrégation des noms : concaténation

1.2. En java aussi, c’est possible

Nous allons maintenant voir comment implémenter la version fonctionnelle en Java. Pour cela nous allons implémenter les fonctions filter (filtrage), map (transformation) et reduce (agrégation), qui seront les bases de la manipulation des listes.

Commençons par la fonction map. Il s’agit d’une fonction qui prend une liste en paramètre et applique une transformation sur chacun de ses éléments.

public <T, R> List<R> map(List<T> input, ? transformation) {
  List<R> output = new ArrayList<R>();
 
  for (T elt : input) {
    output.add(transformation(elt));
  }
 
  return output;
}

La fonction filter quant à elle, prend une liste en paramètre et retourne une nouvelle liste ne comportant que les éléments de la première qui vérifient une condition.

public <T> filter(List<T> input, ? condition) {
  List<T> output = new ArrayList<T>();
 
  for (T elt : input) {
    if (condition(elt)) {
      output.add(elt);
    }
  }
 
  return output;
}

La fonction reduce peut être résumée ainsi : on applique un opérateur entre chaque élément de la liste.

public <T, R> reduce(List<T> input, R elementNeutre, ? agregat) {
  R result = elementNeutre;
 
  for (T elt : input) {
    result = agregat(result, elt);
  }
 
  return result;
}

Pour que ces fonctions soient complètes, il ne nous reste plus qu’à définir la transformation, la condition et l’agrégat. Ce sont en fait trois fonctions :

•	transformation: T -> R
•	condition: T -> boolean
•	agregat: (R, T) -> R

A partir de cette constatation, il est aisé d’écrire les interfaces Function et Function2, que ces fonctions devront implémenter :

public interface Function<T, R> {
  R apply(T input);
}
 
public interface Function2<TL, TR, R> {
  R apply(TL left, TR right);
}

Les fonctions map, filter et reduce sont donc des fonctions qui prennent d’autres fonctions en paramètre : on les appelle des fonctions d’ordre supérieur. Voici les prototypes complets de ces fonctions :

public <T, R> List<R> map(List<T> input, Function<T, R> transformation);
public <T> filter(List<T> input, Function<T, Boolean> filtre);
public <T, R> reduce(List<T> input, R elementNeutre, Function2<R, T, R> agregat);

Nous y sommes ! Nous pouvons maintenant écrire notre fonction getNomsPersonnesMajeures en Java, mais à la sauce fonctionnelle.

public String getNomsPersonnesMajeures(FunctionalList<Personne> personnes) {
  return personnes          // parmi personnes
    .filter(EST_MAJEURE)    // qui vérifient age >= 18
    .map(GET_NOM)           // je veux leurs noms
    .filter(IS_NOT_BLANK)   // qui ne sont pas vides
    .reduce1(CONCATENATION) // et je les concatène
}

Et voici l’implémentation des fonctions passées en paramètre :

public static final Function<Personne, Boolean> EST_MAJEURE = new Function<Personne, Boolean>() {
  public Boolean apply(Personne p) {
    return p.getAge() >= 18;
  }
}
 
public static final Function<Personne, Boolean> GET_NOM = new Function<Personne, String>() {
  public String apply(Personne p) {
    return p.getNom();
  }
}
 
public static final Function<Personne, Boolean> IS_NOT_BLANK = new Function<String, Boolean>() {
  public Boolean apply(String s) {
    return s.isNotBlank();
  }
}
 
public static final Function2<String, String, String> CONCATENATION = new Function2<String, String, String>() {
  public Boolean apply(String left, String right) {
    return left + ", " + right;
  }
}

Vous aurez peut-être remarqué l’utilisation de la fonction reduce1 plutôt que reduce. L’utilisation de reduce(«  », CONCATENATION) aurait donné comme résultat « , Nom1, Nom2, Nom3″; reduce1 est une alternative à reduce qui prend comme élément neutre le premier élément de la liste, ainsi, reduce1(CONCATENATION) retourne bien « Nom1, Nom2, Nom3″.

2. Java 8 change la donne

2.1. La syntaxe

2.1.1. Lambda

Devoir déclarer des objets Function, que ce soient des variables ou des classes anonymes, est lourd et peu lisible en Java : dans les exemples précédents, seules les parties en gras sont réellement utiles. Java 8 introduit une nouvelle syntaxe pour représenter des fonctions anonymes (les lambdas), ainsi

(String a, String b) -> { return a + ", " + b; }

… est une fonction anonyme prenant en entrée deux entiers et retournant leur somme.
Les types des arguments peuvent être omis, ainsi cette expression peut être simplifiée en :

(a, b) -> { return a + ", " + b; }

Si l’expression se contente de retourner une valeur, les accolades et le mot-clef « return » peuvent être omis, ainsi :

(a, b) -> { return a + ", " + b }

Peut être écrit :

(a, b) -> a + ", " + b

Notre fonction CONCATENATION de départ est désormais bien plus simple à écrire, et surtout plus lisible !

Une fonction sans argument :

() -> "Hello world!"

Pour une fonction avec un seul argument, les parenthèses autour de ce dernier peuvent être omises, ainsi :

(n) -> n * n

Peut être écrit :

n -> n * n

2.1.2. Référence de fonction

Jusqu’à présent nous avons vu comment déclarer des fonctions anonymes. Parfois on peut vouloir utiliser une méthode déjà existante. C’est à cette fin que Java 8 introduit les références de fonctions.

Par exemple, la fonction

Math.max(int a, int b)

… peut être référencée par

Math::max

Et ainsi, pour récupérer la plus grande valeur d’une liste, on écrira simplement

public int getMax(List<Integer> liste) {
  return liste.reduce1(Math::max);
}

Il existe 4 types de références de méthode :

Type Formalisme Exemple
Référence vers une méthode statique Classe::methode Math::max
Référence vers une méthode d’instance d’un objet donné objet::methode « chaine »::matches
Référence vers une méthode d’instance d’une classe donnée Classe::methode String::matches
Constructeur Classe::new


2.1.3. Quand peut-on les utiliser ?

Les lambdas et références de méthode peuvent être utilisés partout où une classe SAM est attendue. Une classe SAM (pour Single Abstract Method) est une interface ou une classe abstraite exposant une unique méthode abstraite ; le lambda ou la référence de méthode devant alors avoir le même prototype que ladite méthode. Nous pouvons citer, comme classes SAM, les classes Runnable, ActionListener, Comparator, etc.

2.2. Mise à jour de la bibliothèque standard

Les collections s’enrichissent de nouvelles méthodes s’appuyant sur les lambdas. Nous pouvons citer, par exemple (pour liste = [1, 2, 3, 4]) :

•	map : liste.map(n -> 2*n) => [2, 4, 6, 8]
•	reduce : liste.reduce(0, (a, b) -> a + b) => 10
•	filter : liste.filter(n -> n%2 == 0) => [2, 4]

Voici l’implémentation Java 8 de notre fonction getNomsPersonnesMajeures :

public String getNomsPersonnesMajeures(List<Personne> personnes) {
  return personnes                       // parmi personnes
    .filter(p -> p.getAge() >= 18)       // qui vérifient age >= 18
    .map(p -> p.getNom())                // je veux leurs noms
    .filter(n -> n.isNotBlank())         // qui ne sont pas vides
    .reduce1((res, n) -> res + ", " + n) // et je les concatène
}

Ainsi, plus besoin de déclarer nos méthodes EST_MAJEURE, GET_NOM, IS_NOT_BLANK et CONCATENATION !
En plus d’être plus lisible, cette implémentation peut être plus optimisée car les implémentations des fonctions filter, map, reduce peuvent être paresseuses (n’effectuer la transformation ou le filtrage que si c’est vraiment nécessaire : si la valeur résultante est effectivement appelée plus tard) et parallélisées.

Ainsi, via la fonction parallel, certaines de ces nouvelles opérations peuvent utiliser à leur avantage les architectures multi-cœurs de nos machines actuelles : la fonction précédente peut être parallélisée à moindre frais grâce à la fonction parallel :

public String getNomsPersonnesMajeures(List<Personne> personnes) {
  return personnes                       // parmi personnes
    .parallel()                          // calculer en parallèle
    .filter(p -> p.getAge() >= 18)       // qui vérifient age >= 18
    .map(p -> p.getNom())                // je veux leurs noms
    .filter(n -> n.isNotBlank())         // qui ne sont pas vides
    .reduce1((res, n) -> res + ", " + n) // et je les concatène
}

Je vous laisse le soin d’écrire la version Java 7… Personnellement, le courage me manque.

3. Conclusion

Les fonctions d’ordre supérieur permettent de factoriser davantage le code et donc de :

  • Réduire les risques d’erreur,
  • Avoir une implémentation standard plus maintenable et optimisable,
  • Se concentrer sur le quoi plutôt que sur le comment dans le reste du programme.

En offrant des facilités d’écriture et tout un panel de méthodes tirant parti des lambdas, Java 8 fait un pas vers la programmation fonctionnelle. Il n’est cependant pas nécessaire d’attendre Java 8 pour s’y mettre : des bibliothèques comme Guava offrent déjà des classes et des méthodes facilitant l’écriture de programmes fonctionnels en Java.
Et parce que la programmation fonctionnelle ne se résume pas aux fonctions d’ordre supérieur, il peut être intéressant de regarder ce qui se fait du côté de langages comme Scala, utilisant la JVM et interopérable avec Java, et proposant des fonctionnalités puissantes comme le filtrage par motif (pattern matching), les traits, une bibliothèque standard tournée vers la programmation fonctionnelle, etc.

,

Devoxx 2013, conférence pour les développeurs ayant accueilli pas moins de 1 400 personnes, a été l’occasion d’exposer, entre autres, les tendances à venir concernant les technos web ainsi que l’outillage associé au développement d’applications Web modernes. Sébastien Vaira, Senior Developper chez Infotel nous propose de revenir sur les tendances et outils du dévelopement Web

Ainsi, une des principales tendances serait de déporter l’implémentation du pattern MVC côté client (via JavaScript) et de disposer uniquement d’ «endpoint» REST côté serveur. Par le biais de la présentation d’outils JS divers et variés, nous allons évoquer l’originalité de cette perspective mais aussi les limites actuelles, telles qu’elles ont été abordées durant la conférence.

Backbone JS – librairie légère pour structurer nos applis Web avec le pattern MVC

Comme nous l’avons évoqué précédemment, déporter l’implémentation (en clair) et l’exécution d’une partie plus conséquente du code côté client nécessite d’amener une standardisation, un cadre et donc des bonnes pratiques au JavaScript ; ce « fameux » langage dont on a tellement évoqué le côté permissif par le passé…

Pour répondre à ce besoin, la conférence de Frédéric Camblor fût axée sur Backbone, une petite librairie JS (6.3 kb) pour structurer notre application Web avec le pattern MVC.

Voici en quelques lignes succinctes les axes d’utilisation de cette librairie:

1) Model Backbone

Ci-dessous la définition la plus simple d’une instance de modèle Backbone :

new Book({
  title: "One Thousand and One Nights",
  author: "Scheherazade"
});
 
var TechnosClass = Backbone.Collection.extend({
        model: modelType,
        url: "/data/technos.json",
        defaults: {
        },
 
        initialize: function(properties, classProperties){
            TechnosClass.__super__.initialize.call(this,properties,     classProperties);
        }
 
});

Ou encore un modèle de données défini et branché sur un ‘endpoint’ REST retournant du JSON (même s’il est simulé ci-dessous avec fichier json local) :

var MainRouterClass = Backbone.Router.extend({
 
        routes: {
            "!/listTechnos": "listTechnos"
        },
 
 
        listTechnos: function(){
            ...
            window.view = new TechnoListingView().render();
            ...
        }
});

2) Router Backbone (Contrôleur)

Le « Router Backbone » fournit des méthodes pour le routage des pages côté client en les connectant à des actions et événements.
Ainsi, tout click sur le lien <a href= »#!/listTechnos »> provoque l’exécution de la fonction associée (« listTechnos »), invoquant ici la méthode de rendue de la vue TechnoListingView, présentée ci-après.

3) Backbone View



 
var TechnoListingViewClass = Backbone.View.extend({
 
        render: function(){
            ...
            this.$el.html(viewTemplate({ }));
            ...
        }
 
});

La fonction ‘render’ permet de définir comment mettre à jour le fragment de page.
Maintenant que nous disposons de ce cadre MVC, il reste à coupler Backbone à un ensemble d’outils très pratiques et innovants.

Outillage pour applications Web modernes

SASS

Tout d’abord SASS, qui est un préprocesseur CSS (c’est-à-dire un langage de script compilé et interprété en tant que CSS), apportant plusieurs facilités dont une hiérarchie (« Nesting ») intéressante dans la définition du style d’une vue donnée.

scss
SASS permet également la définition de variables (ex: $blue: #3bbfce;) pour éviter la redondance.

Ou encore, pour un sélecteur donné, l’héritage du style d’un sélecteur annexe.


RequireJS

Outil JS permettant de charger un fragment de script à la demande, de gérer les dépendances entre les scripts, de les charger en asynchrone tout en gardant l’ordre renseigné.

require(["jquery", "jquery.alpha", "jquery.beta"], function($) {
    //the jquery.alpha.js and jquery.beta.js plugins have been loaded.
    $(function() {
        $('body').alpha().beta();
    });
});


On peut également optimiser les scripts afin d’être plus légers et performants.

Plugin ‘Handlebars’

Il s’agit d’un moteur de « templating » côté client ; le navigateur gérant le rendu de nos pages par évaluation d’expressions.

<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{body}}
  </div>
</div>

Dès lors, on peut facilement définir des expressions sur un modèle de données JSON.

Ainsi, cette approche change clairement du fragment HTML usuel renvoyé par le serveur pour interprétation par le navigateur client…

Rivet

RIVET (http://rivetsjs.com/ ) est un framework de binding bi-directionnel (« One-way and two-way binding to/from DOM elements »).
C’est-à-dire :

  • Tout ajout dans le modèle de données JS provoque la MAJ de la vue qui lui était associée
  • Toute édition dans un formulaire valorise le modèle de données.



Comment démarrer rapidement ?

Certains outils existent déjà pour créer des squelettes d’applications respectant les conventions exposées précédemment, c’est-à-dire : créer un ‘artifact’ Backbone standard et permettre de lui brancher les outils annexes décris auparavant.
Ainsi nous évoquerons celui mentionné durant la conférence : «Yeoman ».

YEOMAN est un outil basé sur Node.js et s’exécutant en ligne de commande (MAC OSX/Linux/ Windows).

Quant-à Node.js, c’est un système logiciel côté serveur pour l’écriture évolutive d’applications Web.
Basé sur le moteur JavaScript V8 de Chrome, il fait partie des moteurs JavaScript les plus performants.

Node.js propose de créer votre propre serveur Web par lui-même, permettant ainsi aux développeurs de s’affranchir d’Apache et ainsi de créer une application Web entière (côté serveur et client confondus) en un langage : le JavaScript.

Il s’agit d’une caractéristique de l’outil ; mais on peut très bien l’associer aux serveurs Web usuels…
Finalement, sa grande spécialité est sa partie communication temps réel (WebSocket).

Après cette brève description de Node.js, revenons à Yeoman :
qui, à vrai dire, est une collection de 3 outils regroupés sous ce libellé :
yo grunt bower

  • ‘YO’ permet la création d’un squelette d’application Backbone
yo webapp #scaffold out a skeleton web app project
  • ‘‘GRUNT’ est utilisé pour construire et tester votre projet.

Il permet aussi de :

  1. Lancer un serveur http (fonctionnalité basé sur Node.js) ;
    la commande ‘grunt server’ lançant ainsi un serveur HTTP local sur le port 9000 (par défaut)
  2. Bénéficier du ‘live-reload’ (GUI rafraîchie dès édition des fichiers sources et vice-versa)
    Pour ce faire, un mapping est réalisé entre le serveur web et le Workspace local et via websocket, la vue cliente est rafraîchie dès édition du code source.
    Dès lors, on perçoit rapidement les agréments procurés : toute édition du style CSS sous Chrome par exemple, pour arriver à notre besoin graphique final est répercutée sans effort sous notre IDE : IntelliJ par exemple.
  3. ‘Minifier’ le code JavaScript (par introspection et compression) pour l’optimiser au maximum et permettre des temps de chargement de page divisés par 2, voire plus…
    • ‘BOWER’ provisionne les dépendances pour les applications.

    Il est basé sur le même principe que Maven.

    Au lieu de télécharger les modules JS à la main sur Internet (comme on le fait actuellement) et de les «committer » sur le gestionnaire de sources, on déclare nos dépendances dans un fichier annexe nommé ‘component.json’ (équivalent du POM.xml de Maven).

    Dès lors, cela créé un ‘repository’ local par application et cet entrepôt est valorisé (rempli) par les artifacts rapatriés.

    A noter que, tout comme Maven, Bower gère la transitivité des dépendances gérées.

    {
      "name": "technostore",
      "version": "0.0.0",
      "dependencies": {
        "sass-bootstrap": "~2.3.0",
        "requirejs": "~2.1.4",
        "modernizr": "~2.6.2",
        "jquery": "~1.9.1",
        "backbone": "~1.0.0",
        "underscore": "~1.4.4",
        "require-handlebars-plugin": "~0.4.0",
        "rivets": "~0.4.5"
      },
      "devDependencies": {}
    }

    Partant sur l’intégralité des aspects et solutions décris précédemment, nous possédons désormais tous les outils pour développer des applis Web modernes. Les seuls critères annexes, restant à considérer, étant :

    • l’utilisation de la révision majeure d’HTML : HTML 5 (doté de la capacité de réaliser des connexions entre utilisateurs PeerToPeerConnection() grâce aux Websockets, ou encore supportant le streaming de médias Video et audio)
    • l’utilisation de navigateurs performants tels : « Chrome Canary ».

    D’ailleurs, si vous souhaitez consulter l’application réalisée en mode « live coding » lors de la conférence de Frédéric Camblor, le code source est disponible sous Github à l’adresse suivante :
    https://github.com/fcamblor/technostore

    Limite actuelles
    Les problèmes rencontrés sont essentiellement ceux qui sont le lot de toute technologie en cours d’adoption : une satandardisation encore partielle et une probabilité d’ajustements non superficiels des technologies en jeu. En voici un aperçu, à vous de les juger en fonction de votre situation (souvent imposé par votre projet) et philosophie : Aventurier, Attentiste… ou Conservateur !

    • maturité de YEOMAN
    • finalisation de la spécification HTML5 en 2014
    • Le standard qui établira le WebRTC (Web Real-Time Communication) n’est pas encore complet
    • Chrome n’est généralement pas le navigateur standard chez le client
    • Sécurité : Déporter la quasi intégralité du code côté client (sauf données DB transmises en JSON via un Web service REST par exemple) permet aux utilisateurs malins de scruter les URLS (« endpoints ») des Web services invoqués et plus si affinité…

Nous avons mené depuis deux ans des projets internes autour d’hadoop pour améliorer les traitements que nous opérons sur les grandes bases de données dont nous avons la maîtrise : extraction, migration totale ou partielle vers des bases NoSQL.

Il s’est présenté récemment un cas d’utilisation représentatif des services rendus par les technologies connexes sur lequel nous avons mené une action poussée d’optimisation pour comprendre en profondeur les possibilités de Hive ainsi que les améliorations apportées par une approche de niveau Map / Reduce.

Contexte :

Des fichiers de plusieurs Go servent à la synchronisation d’applications dans les SI de différents clients. Ils obéissent à un format de données propriétaire (en SGML) et variable dans sa structure. Historiquement, des incohérences se sont glissées et induisent des erreurs lors de l’intégration des données et nécessitaient un traitement de purge.

L’approche classique aurait été de créer un développement spécifique, basé sur un parsing des fichiers de structuration dans un système de fichier ou une base de données, puis le développement des règles d’épuration avec une gestion fine de la cohérence.

Pour être le plus réactif possible, nous avons opté pour l’utilisation de notre cluster ‘Labo’ sous Cloudera : il est modeste (4 noeuds seulement), mais efficace. Le cheminement a été le suivant:

  1. Montée des données dans HDFS,
  2. Définition des meta-données qui permettront d’extraire les informations pertinentes :
  3. L’utilisation de balises comme élément de découpage ne permet par de conserver toutes les données pertinentes,
  4. Introduction d’un pré-traitement d’enrichissement du fichier pour préparer les extractions constitué comme suit :
    1. 1- Identification d’une chaîne de traitement,
    2. 2- Création d’une boîte à outils Java Map /Reduce pouvant être exploitée à plus haut niveau (i.e. dans Hive)

L’exécution des traitements par une approche naïve nous a conduit au constat de performances relativement médiocre : 41 minutes. Elle correspond à la séquence suivante :

Enchainement traitements Hive

En détail : L’objectif reste celui de réaliser une jointure entre les lignes possédant une donnée reconnue comme viable et celles similaires possédant un indicateur d’incohérence nommé L019EP.

Deux tables sont ainsi crées :

  • rightD : lignes contenant une donnée valide pour la colonne B (requête SELECT avec clause WHERE)
  • wrongD : lignes contenant une donnée non valide pour la colonne B (requête SELECT avec clause WHERE)
Temps de création des tables
(minutes)
Volumétrie des table (GB)
rightD 4 2
wrongD 17 8



Le résultat final est obtenu à travers la jointure entre les deux tables de données rightD et wrongD. Une jointure simple est alors réalisée sur l’égalité de leurs colonnes A et C et le résultat final est stocké dans la table resultSorted. Le contenu de cette table est alors l’ensemble des lignes ayant été en doublons et possédant une donnée valide pour la colonne B.
Voici la requête utilisée (très simple comme on peut en juger) :

3
4
5
INSERT OVERWRITE TABLE resultSorted SELECT T1.A, T1.B, T1.C  
FROM rightD T1 
JOIN wrongD T2 ON T1.A = T2.A  AND T1.C = T2.C;
Query time (minutes) Table size (GB)
resultSorted 20 15



Contrat rempli : nous avons une solution opérationnelle en moins de deux jours, avec un résultat permettant de purger les applications cibles.

Phase d’optimisation Hive – Map / Reduce

Dans l’optique de rendre récurrent l’exécution de ces traitements, il est nécessaire de se pencher sur les performances et obtenir un meilleur réactivité et une exploitation plus juste des ressources.
Il existe plusieurs leviers d’optimisation :

  • Travailler sur les requêtes,
  • Implémenter des algorithmes plus ‘bas-niveau’,
  • Optimiser son infrastructure (Réseau, disque, mémoire).

(on peut trouver des éléments techniques détaillées à : http://blog.cloudera.com/blog/2009/12/7-tips-for-improving-mapreduce-performance/ )

1- Monter en mémoire les données de faible volumétrie :

Le principe consiste à indiquer lors de la requête la petite table concernée. L’instruction MAPJOIN(petiteTable) permet cela :

INSERT OVERWRITE TABLE resultSorted 
     SELECT /*+ MAPJOIN(T2) */  T1.A, T1.B, T1.C      
     FROM rightD T1 
     JOIN wrongD T2 ON T1.A = T2.A  AND T1.C = T2.C;

Attention au dimensionnement de sa JVM et la mémoire allouée.

2- Implémenter notre traitement en Map / Reduce

Nous allons travailler au plus près des données manipulées en tirant profit de :

  • La rétention et organisation des seules données pertinentes dans notre donnée unitaire,
  • Maximiser la phase Map qui travaille en local pour soulager la phase de Shuffling et de Reduce qui fait ‘souffrir’ notre réseau inter-noeuds.

Algorithme Map Reduce

3- Ajustement des paramètres :

a. On positionne le nombre de reducers dans chacun des jobs pour exploiter au mieux le cluster.

public class RmvDblConf extends Configured implements Tool{
 
        public int run(String[] args) throws Exception {
 
		Configuration conf = getConf;
 
		JobConf job = new JobConf(conf, RmvDblConf.class);
 
		job.setJobName("RmvDblConf");
 
		//Positionnement du nombre de tâches de réduction
		job.setNumReduceTasks(12);
 
		//reutilization de l aJVM
		job.setNumTasksToExecutePerJvm(-1);
 
		FileInputFormat.setInputPaths(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));
...

En répartissant le traitement de la phase Reduce sur l’ensemble du cluster, le temps de traitement total a été divisé par deux.

Process time (minutes) Output file size (MB)
Job ThreeReduceTasks 16 15


4- Paramétrages Hadoop & Système complémentaires

Voici la liste des autres axes d’optimisation qui doivent être mis en regard avec les spécificités de vos traitements (ils ne sont pas pertinents dans tous les cas !) :

  • Distinction des FS d’entrée et de sortie pour limiter la contention,
  • Réutilisation de JVMs,
  • Augmentation de l’espace mémoire attribué à chaque tâche Map et Reduce,
  • Augmentation des threads de copie du disque vers le buffer Reduce,
  • Augmentation du pourcentage de la mémoire libre à consacrer au buffer Reduce,
  • Augmentation de la taille du buffer Map,
  • La compression de données : les fichiers manipulés ne sont pas assez volumineux pour tirer profit de cette approche.

Cette optimisation s’ajuste en effectuant un monitoring précis de l’utilisation des ressources (CPU/ Réseau / Mémoire); voici un des éléments graphiques restitués par Ganglia :

Monitoring Phase Reduce

Consommation des ressources lors de la phase reduce

Le temps de traitement a été divisé par 6 entre l’approche naïve et celle mettant en œuvre l’ensemble des optimisations retenues :

Process time (minutes) Output file size (MB)
Job Map reduce optimized 7 15

Dans le cas où nous devrions travailler sur un autre cluster, l’optimisation passerait par deux étapes:

  • Premièrement, réaliser un audit du cluster afin d’entre autre de déterminer la mémoire totale disponible, connaitre la puissance totale ainsi que le nombre de machines (= nombre de tasktrackers) constituant le cluster et ainsi connaitre le nombre de tâches maximales pouvant être traitées au même moment.
  • Dans un deuxième temps, les jobs développés prendraient en compte ces paramètres avant même de chercher à pousser l’optimisation via les procédures à notre disposition, compression des données en sortie de la phase Map, optimisation d’écriture du code…

Voilà, c’est notre deuxième participation au DevoxxFR (sur deux éditions) et, bien qu’il y ait des redondances d’une année sur l’autre, l’impression qui s’en dégage est celle d’un souffle de fraîcheur et une volonté positive de bouger les lignes; Concrètement, être confronté à la fine fleur du développement et ses retours d’expérience sur des cas très pointus ne peut qu’être profitable :

  • Cela ouvre l’esprit quant à l’application sur nos projets présents ou à venir,
  • On capte indéniablement l’énergie positive qui se dégage de ces success story (… On peut aussi apprendre à la ‘Failure Conference’ http://france.thefailcon.com/) ,
  • On entrevoit notamment ce qui fait les succès des ‘Grands Du Web’ et comment on peut en tirer parti pour nos projets qui doivent composer avec un historique pas toujours simple à adresser

Comprendre les technologies qui motorisent les grands du Web

Exemple : L’utilisation de briques BigData pour booster son architecture et lui permettre de faire face aux défis de volumétrie que nous observons et qui n’en finiront plus de déferler :

  • Hadoop / Storm / Mahout,
  • Implémentation de solutions ‘Cloud’ : CloudBees, Google App Engine…
  • Du SQL au NoSQL (avec des morceaux de ElasticSearch dedans)

Bien plus que Java et la JVM

Toucher concrètement du doigt les nouvelles technologies (plus ou moins matures… il faut savoir faire le tri) donne une furieuse envie de faire bouger les lignes parfois un peu trop rigides (Architecture, Pratiques de développement)… à faire avec discernement bien entendu.

Par exemple : On a pu observer la poursuite de la montée en puissance du JavaScript et sa professionnalisation; on peut naturellement retenir cette approche pour :

  • L’écriture de tests d’acceptance (KarmaJS, PhantomJS, Mocha, Chai…),
  • La réalisation d’applications pures JS + CSS / HTML5 avec Angular / Ember /Backbone.

Autre observation: on en finit plus également de constater les mérites des langages fonctionnels pour leur expressivité et leur robustesse (Scala , Haskell…) et globalement les bénéfices de regarder plus loin que le seul langage Java.
Les combats épiques de Frameworks (Grails vs Play ! ) et de langages ont été à la fois réjouissants et instructifs : il faut juger constamment les technologies qu’on utilise ou qu’on pourrait utiliser et les mettre en perspective de nos propres enjeux.

et après DevoxxFR ?

Dans les jours à venir, nous reviendrons en détail sur un certain nombre de conférences et ateliers, en attendant notre conclusion est la suivante :

Il est nécessaire de cultiver son ouverture aux technologies et méthodes qui nous permettent de mieux répondre aux défis posés par nos projets :

  • C’est plus valorisant de constater qu’une de nos propositions a pu répondre positivement à une problématique (Performance, Accélération des livraisons, Qualité globale),
  • C’est une manière concrète de montrer à notre client et à nos collègues la valeur ajoutée que l’on apporte, au-delà du rôle frustrant à terme de ‘simple réalisateur’.

Cette curiosité et ce savoir-faire se cultivent continuellement (blogs, tutoriaux, projets), mais également au travers des Groupes d’utilisateurs (Java UG, Hadoop UG, Mongo UG, Android UG….) et DevoxxFR constitue un des moments incontournables pour avoir un aperçu de l’effervescence de ces éco-systèmes.

…et peut-être à AngularJS

Au delà du titre provocateur se dessine une des tendances lourdes de la communauté des développeurs Java & Web:l’intérêt croissant pour les Frameworks JavaScript MV* (MVC ou MVVM).

C’est à première vue très surprenant :

  • Dans notre imaginaire, le JavaScript a souvent été une approche d’enrichissement graphique, intégré plus ou moins maladroitement dans les applications Web,
  • Les frameworks Java on offert des paradigmes très orientés ‘serveur’ masquant l’utilisation de JavaScript, participant à la méconnaissance du langage, a la création d’approches techniques lourdes (parfois maladroites) et quelques effets néfastes en termes de maintenance (http://blogdesexperts.infotel.com/?p=660).

Mais récemment, plusieurs facteurs concordants plaident pour le retour sur le devant de la scène du JavaScript :

  • La maturation du HTML5 et plus particulièrement des techniques suivantes, qui se font au travers d’instructions JavaScript dédiées:

    o Web Socket –> Temps réel,

    o LocalStorage –> Mis en cache de données et fonctionnement en mode déconnecté,

    o Canvas –> Dessin et animations en 2D et même 3D,

    o Mode déconnecté –> Une grande partie de la logique métier est présente dans le client.

  • L’avènement du mobile, et l’émergence de solutions cross-platform qui poussent encore plus dans cette direction, avec des approches différentes, traduisant l’effervescence de cet eco-systèmes HTML / CSS / JavaScript:

    o Sencha,

    o Titanium,

    o PhoneGAP…

  • Une multiplication des APIs qui renforce la constitution d’applications qui agrègent des services, et plaide de facto pour des clients plus riches,

    o Prenons l’exemple d’un Intranet typé ‘Réseau Social’ qui regrouperait les communications institutionnelles, l’accès au système RH, la gestion de la connaissance, la communication interne

  • La démocratisation d’interfaces enrichies par l’emploi de jQuery (et autres librairies similaires), et l’habitude prise par les utilisateurs de disposer d’une ergonomie soignée et réactive. Ce qui exige de coordonner la constitution des pages

Et maintenant ?

Première approche : nous poursuivons la même tactique

Nous nous reposons sur des frameworks qui vont encapsuler la réalité des traitements dans un langage tiers :

Deuxième approche : plus proche du Web

  • Retenir des solutions qui se veulent plus proche de l’esprit WEB, laissant une plus grande liberté dans la création d’interfaces : PLAY et Grails par exemple.

Enfin, le choix de frameworks JavaScripts :

  • Leur rôle est de structurer et recueillir le code de la partie cliente : Ecrans, transition, appels aux services. Sans être limité à cela, c’est un point crucial pour la viabilité de telles approches et éviter le javascript ‘Spaghetti’ inmaintenable qui ne manque pas d’apparaître lorsque les fonctionnalités s’enrichissent.
  • Indirectement, ils adressent (partiellement ou complètement) les problématiques suivantes :

    o Mise à jour du DOM (et des informations affichées à l’écran) lors des changements du modèle (et vice-versa)

    o Application de templates, avec des solutions plus ou moins élégantes,

    o Intégration naturelle de librairies tierces (et en JavaScript elles sont vite innombrables),

    o Testabilité.

  • Ils offrent un bénéfice de productivité immédiat dans le développement des interfaces :

    o Chargement immédiat des modifications (une révolution !),

    o Utilisation productive des extensions Chrome / Firefox de développement (et application des modifications HTML / CSS / JavaScript) opérées,

    o Découplage client serveur ne nécessitant pas la disponibilité de solutions complètes

Rapide horizon des solutions du moment

Parmi ceux qui sont aujourd’hui sous le feu des projecteurs, on peut citer Ember.js, Knockout JS, Backbone et AngularJS pour lesquels une comparaison est disponible ici (Elle date de janvier 2012, déjà plusieurs générations sont passées) : http://codebrief.com/2012/01/the-top-10-javascript-mvc-frameworks-reviewed/ .

Signe de maturité, on voit des approches qui proposent des solutions cohérentes comme http://resthub.org

Nos premiers retours d’expérience issus de projets représentatifs (au-delà du simple TodoMVC — http://addyosmani.github.com/todomvc/ — ) ont permis de mettre en évidence des lacunes quasi rédhibitoires (non-maintenabilité et verbosité avec Backbone et Knockout) et de vrais satisfecit avec AngularJS : http://angularjs.org/.

Ce dernier est une approche véritablement sérieuse, élégante, documentée et accessoirement supporté par Google.

En quelques points Angular JS propose :

  • Un découpage clair favorisé par injection de dépendance (Contrôleur, Service, …);
  • L’utilisation de ‘directives’ pour créer des composants réutilisables,
  • Un moteur de rendu qui ne dénature pas (trop) le HTML, incluant des fonctions de filtre, de boucle,…
  • Des fonctions de validation de formulaires, de gestion de l’historique,
  • Une extension Chrome (Batarang) qui rend le débogage très efficace,
  • Une testabilité de premier ordre (Cf. testacular),

Le plus simple est de commencer avec un projet de base bien structuré : https://github.com/angular/angular-seed

Les inconvénients qui peuvent être avancés à ce jour sont : la mise en place du référencement, une compatibilité avec les navigateurs incomplète (IE 7 et 8 notamment), des compétences encore peu répandues, une adoption encore marginale…mais qui l’est de moins en moins.