Extraire les mots-clefs d'un texte

Dans le cadre d'un projet personnel (dont je vous parlerai peut-être une prochaine fois, qui sait ? teasing...), j'éprouvais le besoin d'extraire les mots clefs d'un texte. Je vous présente ici la solution que j'ai élaborée.

Ce que j'appelle les mots clefs d'un texte, c'est une liste de mots représentative du sujet de ce texte.

Ce domaine mélangant informatique et linguistique est appellé natural language processing, ou traitement automatique du langage naturel. C'est la première fois que je suis ammené à cotoyer ce domaine, j'espère que les plus puristes d'entre-vous ne m'en voudront pas...

Première approche...

Une première approche, plutôt naïve avouons le, serait de compter les occurences de chaque mot dans le texte. Ainsi, on peut facilement trouver les mots les plus utilisés dans le texte.

Cependant, cette approche ne fonctionne pas vraiment. Par exemple, les deux mots "maison" et "maisons" seraient comptés séparement alors qu'il s'agit du même mot, l'un est au singulier, l'autre au pluriel. On peut bien-sûr choisir d'enlever les lettres s à la fin des mots, mais dans ce cas là comment gérer les pluriels non réguliers (cheval / chevaux), les différentes conjugaisons, les différents formes des mots, etc...

Bref vous l'aurez compris, c'est plutôt complexe et j'avais l'impression de devoir réinventer la roue. Cela a forcémment déjà été fait ailleurs ! (Moteurs de recherche, recommandation de contenus, etc.)

Une histoire de racines...

Ce processus a en faite un nom, c'est le processus de racinisation (stemming en anglais). En discutant avec un collègue du sujet, il m'indique que Apache Lucene permet de faire de la racinisation. En creusant un peu plus, il s'avère qu'en interne, Lucene utilise Snowball. Snowball est en faite un domain specific language permettant la description d'un ensemble de règles pour effectuer la racinisation des mots dans un language donné.

Il existe des outils pour traduire un script Snowball dans différents languages de programmation, y compris Java (mon préféré !).

Vous trouverez plus d'informations sur http://snowball.tartarus.org/.

Alphabet et mots vides

Avant d'appeler notre Stemmer (programme en charge de la racinisation) pour chaque mot du texte, il faudrait savoir ce qu'est exactement un mot, et si il est vraiment pertinant de le compter.

Alphabet

Je pensais d'abord définir la liste des caractères qui peuvent séparer des mots (espace, apostrophe, saut de ligne, etc.), mais je pense qu'il est plus judicieux de définir la liste des caractères qui peuvent composer un mot. Il s'agit tout simplement des 26 lettres de notre alphabet, des différents caractères accentués et autres signes diacritiques. J'en ai dressé la liste ici : https://github.com/jchampemont/keywords-extractor/blob/blog/src/main/resources/lang/fr/alphabet.txt

Ainsi, un mot est défini comme étant une suite de ces caractères. Les caractères non listés constituent des séparateurs de mots.

Mots vides

Un mot vide (ou stopword en anglais), est un mot qui n'apporte pas de sens au texte, qui a juste une utilité grammaticale. Le projet Snowball en a dressé la liste pour le français ici : http://snowball.tartarus.org/algorithms/french/stop.txt.

J'ai ajouté à ceux ci les deux conjugaisons suivantes du verbe avoir : a et as, qui m'apportaient pas mal de faux positifs par la suite.

Vous l'aurez compris, par la suite on ignorera complètement les mots vides.

Un peu de statistiques...

Après avoir compté les occurences de chaque racine de mot dans le texte, j'ai défini la pertinence de chaque mot-clef comme étant le pourcentage d'apparition de cette racine parmis toutes les autres.

Enfin, j'ai décidé de ne garder qu'une partie de tous les mots clefs d'un texte. En faisant quelques tests, j'ai trouvé que garder les mots-clefs du dernier 25 percentile de la pertinence était une bonne valeur. Ce chiffre nécessite sûrement un réglage suivant la longueur du texte. (Un texte risque d'entraîner une répartition plus importante de la pertinence, donc un nombre plus important de mots clefs séléctionnés.)

Le code et des exemples

Le code est disponible sur mon GitHub : https://github.com/jchampemont/keywords-extractor.

Pour tester mon programme, j'ai choisi 2 articles récents piochés sur le site du Monde.

Exemple 1

L'article utilisé est le suivant : Chômage : quasi-stabilité au mois de juin.

Voici les résultats :

[W='emploi', R=0.0340]

[W='nombr', R=0.0303]

[W='plus', R=0.0303]

[W='an', R=0.0265]

[W='mois', R=0.0227]

[W='catégor', R=0.0227]

[W='demandeur', R=0.0227]

[W='hauss', R=0.01893]

[W='pôl', R=0.01893]

[W='juin', R=0.01893]

Wow ! Pour être honnête, je ne m'attendais pas à de si bon résultats ! Même sans avoir lu l'article, on retrouve pas mal de mots auxquels on pourrait s'attendre dans un article traitant du chomage en France au mois de Juin.

Exemple 2

L'article utilisé est le suivant : Plusieurs centaines de tentatives d’intrusion sur le site du tunnel sous la Manche.

Voici les résultats :

[W='migr', R=0.0407]

[W='eurotunnel', R=0.0259]

[W='tunnel', R=0.0222]

[W='million', R=0.0185]

[W='précis', R=0.0185]

[W='cal', R=0.0185]

[W='sit', R=0.0185]

[W='intrus', R=0.0148]

[W='tentat', R=0.0148]

[W='terminal', R=0.0148]

[W='manch', R=0.0111]

[W='annonc', R=0.0111]

[W='plusieur', R=0.0111]

Encore une fois, je suis plutôt content du résultat. On retrouve la racine "migr" en tête, qui doit correspondre aux mots "migrants", "migrer", "migration", "immigration", etc. La racine cal que l'on retrouve au 6ème rang est sans doute la racinisation de Calais.

La suite ?

On vient de voir qu'en quelques lignes de code, un peu de recherches sur internet, et une petite dose de statistiques, on peut facilement extraire une liste de mots clefs d'un texte. Notre algorithme est bien sûr perfectible, il pourrait notamment prendre en compte les noms propres, les mots composés ou fréquemment associés entre eux, etc.

Les mots clefs "racine" trouvés par notre programme sont bien adapté à l'utilisation par des programmes informatiques, mais ne peuvent pas être communiqués à des utilisateurs. Pour reformer des mots de français correct, on pourra rechercher de nouveau dans le texte un ou plusieurs mots correspondant à chaque racine.

Si ça vous a plu, n'hésitez pas à partager l'article sur vos réseaux !


Tip: Stash missing feature: default reviewers list

In the company I am currently working in, we are using Atlassian Stash. Stash is a web UI for managing GIT repositories in an enterprise context. I really like this tool, but it has some limitations. One of them is particularly bothering me.

Pull request's default reviewers list

We are working with the git feature branch workflow, pull requests and code reviews. When doing a pull request in Stash, we must specify each reviewer's name. It can be quiet bothering when working in a big team.

There is no option in Stash to configure a default reviewers list. Well, there is a feature request opened on Stash's JIRA (STASH-2924: Default reviewers for a repository) but apparently Atlassian is not willing to do anything about it. Some people are suggesting the installation of a plugin, but you need admin access to the Stash instance to install plugins...

My solution: Bookmarklet !

I really like bookmarklets. They allow you to execute some Javascript on any page, within (almost) any browser. After digging into how the "New Pull Request" page works, I came up with this simple Javascript, which allows you to add a list of people as reviewers of a pull request in just one click.

javascript:void(jQuery('#reviewers').val("username1|!|username2|!|username3"))

That's it! You should replace usernameX with the usernames of people from your team and add that as a bookmark in your browser's toolbar.

Please note that visually, nothing is happening. You should not interact at all with the "Reviewers" input.


Démarrer avec Rust !

J'ai (très) récemment décidé de commencer à apprendre Rust. C'est un langage compilé créé par Mozilla en 2009. Une version stable 1.0 du compilateur rustc est disponible depuis mai 2015, et la dernière version stable, 1.1.0, est sortie en juin.

J'en profite pour partager avec vous mes premiers pas, mes trouvailles et mes progrès. Je ne promets rien, j'irai à mon rythme, selon le temps que j'ai de disponible et mes envies.

Je commence aujourd'hui par faire un tour d'horizon de ce que j'ai installé sur mon poste, et des ressources que j'ai trouvé sur internet pour bien démarrer.

rustc et cargo

La première étape consiste à installer rustc et cargo. Si vous utilisez un Linux/Unix (et vous devriez), une seule commande suffit pour installer ces deux outils :

$ curl -sf -L https://static.rust-lang.org/rustup.sh | sh

Ce script considère que vous utilisez sudo sur votre machine, et vous demandera très certainement votre mot de passe administrateur. (Sinon des paquets pour différents OS sont disponibles sur le site officiel du langage.)

rustc est le compilateur rust, alors que cargo est un outil de gestion des dépendances, et de construction d'exécutables.

Un IDE au poil (ou pas...)

La suite logique, c'est d'installer un IDE. Eh bien il n'y en a pas encore vraiment. Il y a bien quelques tentatives sur GitHub, mais elles m'ont semblées peu abouties.

Visiblement, la communauté utilise plutôt des éditeurs de textes simples (exit Emac donc...) comme Vim (pour les plus courageux), SublimeText (pour les plus riches) ou Atom.

N'étant ni riche, ni courageux, j'ai décidé de me tourner vers Atom (que je connaissais déjà !). Je passe sur l'installation d'Atom pour m'intéresser à celle des différents paquets facilitant la vie du développeur Rust avec cet éditeur.

Coloration syntaxique

Le paquet language-rust apporte la coloration syntaxique à l'éditeur ainsi que divers autres petits bonus. Vous pouvez l'installer via l'interface graphique, ou directement via la ligne de commande avec un simple :

$ apm install language-rust

Complétion de code

Un peu plus compliqué cette fois, il faut d'abord installer racer disponible ici : https://github.com/phildawes/racer.

Il faut aussi avoir téléchargé/décompressé le code source de rustc disponible sur le site officiel.

Finalement, installer le paquet racer dans Atom : https://atom.io/packages/racer. Il faudra compléter dans les options les emplacements de racer et du code source de rustc sur votre disque.

Compilation "à la volée"

L'installation des paquets lint et lint-rust permet la compilation à la volée de vos sources et le soulignage en rouge des erreurs de compilation, à chaque ouverture/sauvegarde de fichier.

Ressources et tutoriaux

On notera d'abord l'existence du livre officiel "The Rust programming language" à cette adresse : https://doc.rust-lang.org/stable/book/. Il me semble suffisant à une première approche des bases du langage.

Le guide officiel de cargo devrait lui vous permettre d'appréhender la construction et la gestion des dépendances avec cet outil : http://doc.crates.io/guide.html.

Sur Reddit, un subreddit est dédié à la communauté Rust : https://www.reddit.com/r/rust/.

Enfin, vous pouvez consulter ce site : This Week in Rust qui propose chaque semaine une newsletter (mais pas par mail) sur les dernières actualités de la communauté.


Devoxx France 2015 Jour 2 : Spring 4.1 et Java 8

Cet article est une reprise d'un article que j'ai publié sur le blog Ippon : Devoxx France 2015 Jour 2 : Spring 4.1 et Java 8.

J’ai eu la chance de participer à Devoxx France, et je souhaitais revenir sur la conférence de Juergen Hoeller et Stéphane Nicoll intitulée “Modern Enterprise Java Architectures with Spring 4.1″.

La conférence était divisée en deux parties : une première partie animée par Juergen Hoeller, qui revenait sur quelques principes de conception du célèbre Framework Java tout en se basant sur les nouveautés introduites dans les versions 4.0 et 4.1 relatives à Java 8 (mais pas que). Et la seconde partie animée par Stéphane Nicoll, qui fut une courte (7 minutes seulement) session de “live-coding”.

Dans cet article, je me concentrerai sur quelques nouveautés évoquées par Juergen, celles qui m’ont semblées être les plus intéressantes ou représentatives de ce qu’apporte la dernière version de Java.

Certaines de ces nouveautés n’en sont pas vraiment, puisque Spring 4.0 est sorti il y a plus d’un an, mais une petite piqure de rappel ne fait jamais de mal.

Beans génériques

En plus de l'annotation @Qualifier, Spring traite désormais les types génériques comme une sorte de qualifier.

@Bean
public GenericService<Customer> myCustomerService() {...}

@Bean
public GenericService<Basket> myBasketService() {...}

@Service
public class BookStoreService {

	@Autowired
    public BookStoreService(GenericService<Customer> customerService) {...}
}

Dans cet exemple, Spring injectera le bon bean. Avant, on aurait eu droit à la fameuse exception NoSuchBeanDefinitionException: No unique bean of type ....

Injection de java.util.Optional

Java 8 a apporté une version directement intégrée à la VM du pattern "Optional" popularisé par Guava.

Spring injectera désormais le bean de type demandé si il existe, Optional.absent() sinon.

@Autowired
public MyBeanConstructor(Optional<GenericService<Customer>> customerService) {
    if(customerService.isPresent()) { ... }
    ...
}

On est ici très proche du fonctionnement de @Autowired(required="false"), si ce n'est qu'avec ce dernier le champ injecté aurait été null.

Annotations

Meta annotations et surcharge d'attributs

Pour rappel, depuis la version 3, Spring permet de combiner plusieurs annotations Spring en une méta annotation :

@Service
@Scope("session")
@Transactional(rollbackFor=Exception.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {}

Dans notre exemple, un bean annoté @MyService sera équivalent à un bean annoté @Service, @Scope("session") et @Transactional(rollbackFor=Exception.class).

Spring 4 permet désormais d'exposer les attributs des annotations sous-jacentes via la méta annotation :

@Transactional
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
    boolean readOnly() default "false";
}

Ici l'annotation @MyService expose l'attribut readOnly de @Transactional. Si aucune valeur par défaut n'est précisée dans la méta annotation, l'attribut devient obligatoire.

Evidemment, cela ne fonctionne pas pour value().

Annotations multiples

Avec les annotations répétables de Java 8, il est désormais possible d'annoter plusieurs fois une méthode, une classe ou un attribut avec la même annotation :

@Scheduled(cron="0 0 12 * * ?")
@Scheduled(cron="0 0 18 * * ?")
public void performCleanup() {...}

JdbcTemplate et les Lambdas

Je pense que les classes *Template étaient les plus à même de bénéficier de l'ajout des expressions lambdas à Java. Un exemple vaut mieux que mille mots, et le voici :

JdbcTemplate jt = new JdbcTemplate(dataSource);
jt.query("SELECT name, age FROM emp WHERE dep = ?",
   ps -> ps.setString(1, "Sales"),
   (rs, rowNum) -> new Person(rs.getString(1), rs.getInt(2))
);

La syntaxe perd un peu de son intérèt si on a besoin de plusieurs lignes pour instancier notre objet métier, mais profite du sucre syntaxique apporté par les lambdas.

Un peu plus près des... standards !

Pour finir, Spring prends désormais en charge les annotations de la JSR 107 (mise en cache) en plus des JSR 250 et 330 sans surplus de configuration.

Un mot sur Spring 4.2

Juergen a aussi évoqué que la première release candidate de Spring 4.2 est attendue pour la fin du mois de mai, et celle-ci apportera les nouveautées suivantes :

  • Support d'évenements émis par le serveur en Spring MVC
  • Support CORS (Cross-origin resource sharing)
  • Support des annotations sur les méthodes par défaut des interfaces Java 8
  • Formattage et binding pour la JSR-354 (Argent et devises)
  • Support Hibernate 5.0 (API native et JPA)

Introduction à Dropwizard

Cet article est une reprise d'un article que j'ai publié sur le blog Ippon : Introduction à Dropwizard.

Kézako ?

Dropwizard est un framework complet et éprouvé (première version publiée en 2011) pour faciliter le développement de services web “RESTful”. Il s’agit en fait du regroupement et de l'intégration de plusieurs outils du monde Java, parmi lesquels : Jetty, Jersey, Jackson, Logback, Liquibase, Hibernate…

J’ai eu l’occasion d’utiliser cet outil et je le trouve particulièrement efficace, notamment pour mettre en place rapidement un ensemble de micro-services, ou un back-end simple pour une application de type Single Page Application (AngularJS, etc.). Dans l’idée, Spring Boot est assez similaire, si ce n’est que Dropwizard n’est pas ancré dans l’écosystème Spring.

Je vous propose ici un rapide tour d’horizon des différentes fonctionnalités de l'outil.

Pour commencer…

Avec Maven, le démarrage d'un projet Dropwizard est plutôt simple, il suffit d'ajouter la dépendance à la dernière version du projet "core" (0.7.1) :

<dependencies>
	<dependency>
		<groupId>io.dropwizard</groupId>
		<artifactId>dropwizard-core</artifactId>
		<version>0.7.1</version>
	</dependency>
</dependencies>

Un peu de configuration

Chaque application Dropwizard a besoin d'une classe de configuration, qui étend io.dropwizard.Configuration. Voici notre classe de configuration, très simple, pour démarrer ce tutorial :

public class MyConfiguration extends Configuration {

    @NotEmpty
    private String environment;

    @JsonProperty
    public String getEnvironment() {
    	return environment;
    }

    @JsonProperty
    public void setEnvironment(String environment) {
    	this.environment = environment;
    }
}

Rien d'extraordinaire ici, mais il faut noter deux choses :

  • La configuration sera dé-sérialisée depuis un fichier YAML par Jackson. Donc il est possible (voir recommandé) d'utiliser les annotations de Jackson !
  • La configuration est validée avec Hibernate Validator ! La liste des contraintes possibles est vaste (voir ici pour la liste complète), et l'application ne démarrera pas si le fichier de configuration n'est pas valide !

Voici le contenu du fichier de configuration correspondant à notre classe :

environment: "dev"

Une application...

Passons maintenant au coeur de notre application, la classe MyApplication qui étend io.dropwizard.Application

public class MyApplication extends Application<MyConfiguration> {

    public static void main(String[] args) throws Exception {
    	new MyApplication().run(args);
    }

    @Override
    public String getName() {
        return "my-application";
    }

    @Override
    public void initialize(Bootstrap<MyConfiguration> bootstrap) {
    	// TODO Auto-generated method stub
    }

    @Override
    public void run(MyConfiguration configuration, Environment environment) 
        throws Exception {
	    // TODO Auto-generated method stub
	    System.out.println(getName() + " is now running on " +
            configuration.getEnvironment() + " environment !");
    }
}

Là encore, rien d'extraordinaire. Un simple classe avec un main qui délègue son exécution à la méthode run. La méthode initialize permet de configurer des composants avant le lancement de l'application.

... pleine de ressources !

Et pour finir, une resource très simple qui nous fera de l'écho :

@Path("/echo")
@Produces(MediaType.TEXT_PLAIN)
public class EchoResource {

    @GET
    @Timed
    public String echo(@QueryParam("echo") Optional<String> echo) {
    	if(echo.isPresent()) {
        	return echo.get();
        } else {
            throw new NotFoundException();
        }
    }
}

Quelques remarques à propos de cette classe :

  • Elle utilise les annotations "standard" de JAX-RS
  • L'annotation @Timed permet à Dropwizard de récolter des métriques sur les appels à cette méthode
  • Dropwizard ajoute le support natif des Optional de Guava à Jersey. Ainsi, si le paramètre echo n'est pas passé, Optional sera absent, sinon il contiendra la valeur du paramètre.

Il ne nous reste plus qu'à déclarer notre ressource dans la méthode run de la classe principale avec un simple environment.jersey().register(new EchoResource());, et on peut passer au démarrage de notre application !

Lancement de l'application

Une des préconisations de la documentation Dropwizard, c'est de construire un fat JAR contenant tous les .class de notre application, y compris les dépendances.

Cela permet de lancer l'application avec un simple java -jar monJar.jar. Pour configurer Maven pour packager un fat JAR, il suffit d'ajouter le plugin suivant dans la sections build > plugins de votre pom.xml :

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>1.6</version>
    <configuration>
	    <createDependencyReducedPom>true</createDependencyReducedPom>
		    <filters>
			    <filter>
				    <artifact>*:*</artifact>
				    <excludes>
					    <exclude>META-INF/*.SF</exclude>
					    <exclude>META-INF/*.DSA</exclude>
					    <exclude>META-INF/*.RSA</exclude>
				    </excludes>
			    </filter>
		    </filters>
	</configuration>
	<executions>
		<execution>
			<phase>package</phase>
			<goals>
				<goal>shade</goal>
			</goals>
			<configuration>
				<transformers>
					<transformer implementation=
                    "org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
                    <transformer implementation=
                    "org.apache.maven.plugins.shade.resource.ManifestResourceTransformer" />     
                        <mainClass>
                             com.jeanchampemont.blog.example.dropwizard.intro.MyApplication
                        </mainClass>
					</transformer>
				</transformers>
			</configuration>
		</execution>
	</executions>
</plugin>

Sans oublier de changer la classe principale de l'application.

L'exécution de la commande java -jar monJar-0.0.1-SNAPSHOT.jar server dev.yaml démarre l'application dans un serveur Jetty embedded.

Un rapide test nous donne le résultat escompté :

$ curl localhost:8080/echo?echo=Hello,%20world!
Hello, world!
$

L'administration

Une des valeurs ajoutées de Dropwizard, c'est certainement l'interface d'administration disponible sur le port 8081 une fois l'application lancée.

$ curl http://localhost:8081/metrics?pretty=true
{
"version" : "3.0.0",
"gauges" : {
	...
    "jvm.memory.total.max" : {
      "value" : 3797417983
    },
    "jvm.memory.total.used" : {
      "value" : 146297864
    },
    "jvm.threads.count" : {
      "value" : 37
    },
    ...
  },
  "counters" : {
    "io.dropwizard.jetty.MutableServletContextHandler.active-dispatches" : {
      "count" : 0
    },
    "io.dropwizard.jetty.MutableServletContextHandler.active-requests" : {
      "count" : 0
    },
    "io.dropwizard.jetty.MutableServletContextHandler.active-suspended" : {
      "count" : 0
    }
  },
  "histograms" : { },
  "meters" : {
    ...
    "io.dropwizard.jetty.MutableServletContextHandler.4xx-responses" : {
      "count" : 3,
      "m15_rate" : 2.717586079541239E-5,
      "m1_rate" : 5.0046012383445376E-33,
      "m5_rate" : 5.610150977893677E-9,
      "mean_rate" : 3.759305958043252E-4,
      "units" : "events/second"
    },
    ...
  },
  "timers" : {
      "com.jeanchampemont.blog.example.dropwizard.intro.resources.EchoResource.echo" : {
      "count" : 8,
      "max" : 0.00173718,
      "mean" : 4.881765E-4,
      "min" : 3.4411E-5,
      "p50" : 1.029335E-4,
      "p75" : 0.0012517205000000002,
      "p95" : 0.00173718,
      "p98" : 0.00173718,
      "p99" : 0.00173718,
      "p999" : 0.00173718,
      "stddev" : 7.335333511812145E-4,
      "m15_rate" : 2.0470100691740497E-4,
      "m1_rate" : 2.914566711610656E-15,
      "m5_rate" : 9.373552096002814E-6,
      "mean_rate" : 0.0010024795256175021,
      "duration_units" : "seconds",
      "rate_units" : "calls/second"
    },
    ...
}

En plus d'avoir accès à différentes statistiques sur l'utilisation des ressources de la JVM, des requètes, des codes de réponse, on remarque que l'annotation @Timed précedemment ajoutée à notre méthode, ajoute des informations sur ses temps d'exécution et leur répartition statistique.

Bilan de santé

Vous avez peut être remarqué, lors du démarrage de notre application, un message plutôt alarmant dans les logs :

WARN  [2014-42-42 42:42:42,642] io.dropwizard.setup.AdminEnvironment: 
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!    THIS APPLICATION HAS NO HEALTHCHECKS. THIS MEANS YOU WILL NEVER KNOW      !
!     IF IT DIES IN PRODUCTION, WHICH MEANS YOU WILL NEVER KNOW IF YOU'RE      !
!    LETTING YOUR USERS DOWN. YOU SHOULD ADD A HEALTHCHECK FOR EACH OF YOUR    !
!         APPLICATION'S DEPENDENCIES WHICH FULLY (BUT LIGHTLY) TESTS IT.       !
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Un Health Check (ou bilan de santé) est un test de la disponibilité d'un service externe indispensable au fonctionnement de notre application : connectivité à la base de données, disponibilité de services web tiers, etc.

Je vous propose ici un StupidHealthCheck qui échoue de temps en temps (à ne surtout pas utiliser en production donc !)

public class StupidHealthCheck extends HealthCheck {

    private Random rand = new Random();

    @Override
    protected Result check() throws Exception {
	    if(rand.nextInt(100) >= 1) {
	    	return Result.healthy();
	    } else {
	    	return Result.unhealthy("fail");
	    }
    }
}

Comme précédement, on n'oublie pas de déclarer notre Health Check dans la méthode run de la classe principale avec un : environment.healthChecks().register("Stupid HealthCheck", new StupidHealthCheck());.

Une entrée est dès lors disponible dans l'interface d'administration et permet de superviser simplement le bon fonctionnement de l'application.

$ curl http://localhost:8081/healthcheck?pretty=true
{
  "Stupid HealthCheck" : {
    "healthy" : true
  },
  "deadlocks" : {
    "healthy" : true
  }
}

Pour finir, Dropwizard apporte son lot de fonctionnalité et s'intègre avec de nombreux outils Java communément utilisés. Je vous encourage à regarder la documentation officielle pour plus de détails. Au final, la seule chose qu'il manque peut être à Dropwizard, c'est l'intégration d'un framework d'injection de dépendance.


Installation d'un serveur de commentaire libre : Isso

Je voulais que ce blog ne soit pas qu'un site ou je partage des informations, mais un site participatif où les visiteurs peuvent participer et apporter leurs avis, correctifs et expertises, via des commentaires.

Un vrai blog quoi...

Des commentaires ? Oui mais...

Ghost ne propose pas (encore ?) la gestion de commentaires.

Un rapide tour des solutions disponibles sur le web dégage deux approches facilement réalisables :

  • Ce qu'on peut appeler les CaaS (Comments As A Service), le plus célèbre étant certainement Disqus. Le principe est simple : un morceau de code à intégrer dans les pages, le service s'occupe du reste.
  • Intégrer un des clones open source de Disqus. Ces logiciels, que j'appelle "serveur de commentaire", sont en faite des petites applications, à héberger soit même, qui prennent en charge la gestion des commentaires sur un ou plusieurs sites. Ça tombe bien, Korben en a dressé une liste ici il y a quelques jours.

C'est la deuxième approche que j'ai choisie, et plus exactement Isso.

Pour moi, l'avantage de cette solution, c'est que je reste maître de mes données et de ce qui est affiché/chargé sur mon blog.

Installation de Isso

Je détaillerai ici la démarche pour installer Isso en production, avec un Nginx en reverse-proxy, sur Ubuntu 14.04.

Vous aurez besoin de Python 2.6, 2.7 ou 3.3. Je ne m'intéresse pas à son installation qui devrait être relativement simple suivant votre distribution.

Pour installer l'environnement Isso, nous allons utiliser VirtualEnv, qui est un outil qui permet d'isoler des installations de Python, et faire ainsi cohabiter sans limite différentes versions de Python et de ses librairies.

# Installation de virtualenv
sudo apt-get install python-setuptools python-virtualenv

Sur ma machine, j'ai créé un utilisteur "python" qui a pour home /apps/python. J'effectue les commandes suivantes en tant que cet utilisateur.

# Création de l'environnement virtuel pour Isso
virtualenv /apps/python/isso
# Activation de l'environnement virtuel
source /apps/python/isso/bin/activate
# Installation de Isso
pip install isso

Et voilà c'est fait ! Enfin presque... Isso a besoin d'un fichier de configuration pour démarrer.

Configuration de Isso

Voici le mien, que j'ai placé dans /apps/python/isso/etc/isso.conf

[general]
dbpath = /apps/python/isso/comments.db
host = http://blog.jeanchampemont.com/
notify = smtp

[smtp]
host = localhost
port = 25
from = isso@jeanchampemont.com
to = votre.email@fai.fr

[server]
listen = http://localhost:9876
reload = off
profile = off

[guard]
enabled = true
ratelimit = 2
direct-reply = 3
reply-to-self = false

Ce fichier est divisé en plusieurs sections :

  • general : on donne ici l'emplacement du fichier SQLite utilisé pour le stockage des commentaire, la liste des adresses autorisées à afficher/publier les commentaires (utilisé pour positionner les en-têtes http de CORS), et la méthode de notification pour les nouveaux commentaires.
  • smtp : adresse, port et e-mail d'origine pour l'envoi des mails de notification
  • server : il s'agit cette fois de préciser sur quelle adresse le serveur de commentaire est à l'écoute des connexions.
  • guard permet de limiter le spam

La documentation étant plutôt bien faite, je vous encourage à aller la consulter pour vous faire une idée des autres options possibles.

(Re)démarrage automatique avec Supervisord

Supervisord est un outil pour superviser des processus que j'affectionne tout particulièrement. Il est plutôt simple d'utilisation, et fait exactement ce qu'on lui demande, c'est à dire lancer un ensemble de processus au démarrage du système, et les rédémarrer en cas de crash.

Voici mon fichier de configuration pour un (re)démarrage automatique de Isso :

[program:isso]
command = /apps/python/isso/local/bin/isso -c /apps/python/isso/etc/isso.conf run
user = python
autostart = true
autorestart = true

Configuration de Nginx

Voici la configuration de mes Virtualhost sous Nginx pour un accès en https au serveur de commentaire :

server {
	listen 80;
    server_name comments.jeanchampemont.com;
	return 301 https://$server_name$request_uri;
}

server {
	listen 443 ssl;
	server_name comments.jeanchampemont.com;
	ssl_certificate /chemin/vers/mon/certificat;
    ssl_certificate_key /chemin/vers/ma/clef/privee;

	access_log /var/log/nginx/comments.jeanchampemont.com_access.log;
    error_log /var/log/nginx/comments.jeanchampemont.com_error.log;

	location / {
    	proxy_pass http://localhost:9876;
	    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header Host $host;
    	proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Utilisation de Isso sur Ghost

Allez courage, c'est bientôt finis, et ça tombe bien, c'est très certainement la partie la plus simple !

Les commentaires dans Isso sont gérés en fonction de l'URL d'appel. Par exemple, si j'appelle le module de commentaire sur la page http://blog.jeanchampemont.com/bienvenue-sur-ghost/ , les commentaires qui y sont publiés sont associés à l'URL http://blog.jeanchampemont.com/bienvenue-sur-ghost/.

Pour l'intégration du module de commentaires pour la page en cours, les deux lignes suivantes suffisent :

<script data-isso="https://comments.jeanchampemont.com" src="https://comments.jeanchampemont.com/js/embed.min.js"></script>
<section id="isso-thread"></section>

Pour afficher un compteur de commentaires sur la liste des articles, c'est tout aussi simple :

<a href="http://url/vers/larticle/#isso-thread">commentaires</a>

En incluant une seule fois le script suivant :

<script data-isso="https://comments.jeanchampemont.com" src="https://comments.jeanchampemont.com/js/count.min.js"></script>

Ce script va rechercher tous les liens vers #isso-thread, y injecter le nombre de commentaire pour l'article en question, et gérer le singulier/pluriel du mot qui suit. (enlever ou non le 's' à la fin).

Vous pouvez retrouver mon thème pour ghost qui inclus l'utilisation de Isso sur mon github.


Bienvenue sur mon blog !

Il en faut toujours un, voici le premier article de mon blog.

Peu de contenu, mais celui-ci a plus vocation à être un simple test plutôt qu'un vrai article.

À très vite pour la suite !