Pages

jeudi 7 août 2014

Automatisation des releases chez Santéclair via SNVKit et développement d'un Mojo Maven

Problème

La procédure de livraison d'applications à partir de la trunk est complexe et fastidieuse chez Santéclair.
Si le nombre de livrables est important, cela peut prendre jusqu'à une demie journée d'actions répétitives laissant place à des erreurs de manipulation et des oublis.

Afin d'avoir un ordre d'idée du nombre d'actions à effectuer par projet, en voici la liste :

  • Faire un commit de vos modifications et faire un merge avec la branche maintenance
  •  Résoudre les conflits
  •  Aligner la version du super-pom si besoin
  •  Faire un commit final
  •  Lancer la tâche de release pour créer le nouveau tag
  •  Renommer la branche maintenance en maintenance-V.R.x
  •  Switcher sur cette branche maintenance-V.R.x
  •  Changer la valeur de la balise scm --> developerConnection dans le pom : changer maintenance par maintenance-V.R.x
  •  Switcher sur la trunk
  •  Créer la nouvelle branche maintenance à partir du trunk
  •  Faire évoluer la version du pom de la trunk : V.R+1.0-SNAPSHOT
  •  Faire un commit sur la trunk
  •  Dans le cas d'une bibliothèque (.jar), faire un deploy de la trunk pour l'ajouter à Nexus.
  •  Switcher sur la branche maintenance
  •  Changer la valeur de la balise scm --> developerConnection dans le pom de la maintenance : changer trunk par branches/maintenance
  •  Faire un commit sur la maintenance
  •  Dans le cas d'une bibliothèque (.jar), faire un deploy de la trunk pour l'ajouter à Nexus.
  •  Dans le cas d'un projet web (.war), brancher le projet sur la version que l'on souhaite livrer et lancer la tâche correspondante.


Beaucoup de tâches étant automatisables, la seule chose qui restait à faire était de se pencher sur les solutions envisageables et compatibles avec notre SI.

Solution


La solution retenue fut celle d'utiliser SVNKit afin de gérer toutes les tâches manuelles de :
- commit / update
- switch
- tag / release
- vérification que le repo distant et le workspace sont synchronisés ..etc 

Le développement d'un plugin Mojo semblait aussi nécessaire afin de pouvoir intégrer toutes ces tâches dans le cycle de vie du build Maven.

Les autres actions de modification et de renommage de fichiers...etc se feront via les classes utilitaires du pakage java.nio.

Mise en place


D'emblée, il est nécessaire de spécifier à Maven le chemin vers la JDK (si ce n'est pas déjà fait) en ajoutant @SET JAVA_HOME=C:\Program Files\Java\jdk1.7.0_25 au fichier mvn.bat du repertoire maven d'installation.

Le projet maven-plugin-fwk contient la classe ReleaseMojo.java, qui constitue notre plugin Mojo.
Ce projet a trois dépendances :



Le développement d'un Mojo Maven se fait en étendant la classe AbstractMojo.

La description du plugin doit être fournie, soit dans le pom.xml via les balises <goal> et <phase>, soit par un tag Javadoc au niveau de la classe java définissant le Mojo :


C'est la deuxième solution que j'ai retenue car elle me permet de tout centraliser au niveau du Mojo.

Certaines informations comme l'emplacement du projet seront utiles pour la livraison. Elles peuvent être récupérées de la même manière via le tag Javadoc @parameter :


Depuis Maven 3, il est aussi possible d'injecter ces valeurs par défaut via l'annotation @Parameter.

Il est aussi possible de récupérer des paramètres spécifiés en ligne de commande lors de l'exécution du plugin.

Ensuite, tout se fait dans la méthode execute() du Mojo à savoir :

1 - L'initialisation nécessaire pour la prise en compte de tous les protocoles :


2 - La connexion au répository SVN via la classe SVNWCUtil :


3 - La récupération d'une instance de SVNClientManager permettant d'effectuer le reste des opérations : changement d'emplacement sur le svn distant, commit, update, switch...etc :


Quelques actions SVN via SVNKit :


- L'action de switch se fait de la manière suivante :


- L'action de copie d'un répertoire d'un emplacement vers un autre sur le répository SVN distant se fait de la manière suivante :



J'utilise ce code pour sauvegarder l'ancienne branche Maintenance.

- L'action de commit du fichier pom.xml :



Le plugin Maven-invoker


Il va nous permettre d'exécuter le goal Maven de release. Voici comment je l'utilise :


 
Si un paramètres packageMe est spécifié en ligne de commande lors de l'exécution du plugin, je lance la tâche maven de package :


Optimisation


Avant de lancer tout le process de release je vérifie qu'il n'y a pas de différence ou de conflit entre les sources du workspace et les sources présentes dans le répository SVN distant :

Lancement du plugin


Il faut tout d'abord déployer le plugin via la commande mvn clean deploy.

Puis se positionner sur le prjet à livrer et lancer la commande : mvn:groupId:artifactId:version:goald ou encore mvn santeclair:maven-plugin-fwk:1.0.0:release-trunk

lundi 9 juin 2014

Embarquer un environnement OSGi dans un tomcat

OSGi est un framework permettant d'organiser l'architecture de vos applications en petits modules indépendants. Pour ce faire, il est nécessaire de déployer une implémentation d'OSGi afin que vos modules soient gérés. L'avantage avec OSGi c'est que vous avez le choix mais c'est également un inconvénient car il peut s'avérer compliqué de faire le bon.

Dans le contexte de Santéclair, nous possédons déjà un environnement Java JEE sous Tomcat 7. Ce dernier est utilisé comme un serveur d'applications léger et fait très bien ce qu'on lui demande. La structure de Santéclair, ces besoins et ces effectifs ne nous permettent pas d'envisager le déploiement d'un autre serveur : nous maîtrisons bien Tomcat, nous l'utilisons depuis longtemps. Cela exclu donc de faire le choix d'OSGi comme socle du serveur, donc pas de Virgo (excellente solution si toutefois vous souhaitiez faire de l'OSGi et que vous n'avez pas de contraintes de déploiement)

Il nous reste donc la seconde solution : embarquer un environnement OSGi dans un Tomcat. Cette solution présente l'avantage de s'appuyer sur un socle robuste et largement éprouvé : Tomcat. De plus, nous verrons par la suite qu'il est possible de profiter des librairies déjà en place dans votre Tomcat (shared / WEB-INF/lib) en les exposant dans votre environnement OSGi.

Pour l'implémentation d'OSGi, notre choix s'est naturellement tourné vers felix. Ce dernier est édité par apache, il devrait donc bien s'entendre avec Tomcat. De plus, il est assez réputé dans la communauté OSGi et est intégré dans d'autres solutions embarquant de l'OSGi. Une autre possibilité aurait été Equinox (moteur OSGi d'Eclipse, si, si, Eclipse s'appuie sur OSGi) mais ce dernier semble plus lourd. Toutefois, si vous préférez cette implémentation, il existe ici un article qui explique comment embarquer un Equinox dans un Tomcat.

Le vif du sujet

Embarquer un environnement OSGi dans un Tomcat consiste à déployer un WAR qui sera charger de démarrer felix. Donc il vous faut d'abord un projet web. Les captures d'écrans et développement pour cet article seront réalisés sous Eclipse Kepler.

Tout d'abord les dépendances maven. Pour démarrer un felix, il faut felix et une dépendance OSGi compendium :
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.main</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
Enfin, comme nous sommes entre gens de bonne société, on ajoute les dépendances spring qui vont bien. Attention, ici pas de spring 4, uniquement du 3 pour des raisons de compatibilité avec les librairies gemini qui seront utilisées dans un prochain tuto :
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
Ok, on est prêt pour le coeur du probème. Ci dessous, vous trouverez l'organisation du projet :


On a donc le package "santeclair.portal" qui contient deux classes :
  • L'interface FelixLauncher qui nous permet de décrire les méthodes qui seront appelées pour lancer et stopper felix
  • La classe abstraite AbstractFelixLauncher qui implémente les comportements par défaut des signatures de l'interface (pas de JAVA 8 ici...)
Ensuite le package "santeclair.portal.config" qui contient :
  • L'interface InitConfig qui décrit la maps que doit retourner les config possibles pour démarrer felix
  • Plusieurs config type :
    • FileInstallConfig : permet de démarrer ou de stopper automatiquement des bundles simplement avec le système de fichier
    • FrameworkConfig : config initial du framework
    • WebEmbeddedConfig : config permettant d'embarquer l'environnement OSGi dans un war
On aurait évidemment put faire un seul fichier de config qui aurait contenu tous les fichiers de config en un seul mais OSGi est un framework qui est à la fois le contenu et le contenant. Notre façon d'implémenter OSGi garde à l'esprit cette philosophie et la méthode d'implémentation présentée ici pourra être facilement adaptée à d'autres situations que de l'embarqué dans un tomcat.

Enfin, nous avons le moteur a proprement parlé dans le package "santeclair.portal.webapp". On y trouve :
  • FelixLauncherWebappImpl : l'implémentation de notre classe abstraite FelixLauncher 
  • HostActivator : l'activateur par défaut qui permettra de créer le pont entre l'environnement OSGi et le classloader du war
  • HostedFelix : un composant spring qui sera en charge de démarrer felix et de l'arrêter simplement grâce à des postConstruct et preDestroy.

La vérité est dans le code


Pour voir le code en question, rien de tel que de le visualiser sur github : 
https://github.com/telligcirdec/osgiTomcatEmbedded/tree/master/osgi-embedded
 Le point de départ de l'implémentation est la classe FelixLauncherWebappImpl :

Dans ce code, on définit trois variables d'instance importantes :
  • props : cette variable contient les propriétés de démarrage que l'on fourni à felix. Dans notre cas ces propriétés sont mis à disposition par spring depuis un fichier de config nommé felix.properties
  • rootDir : qui détermine le dossier racine de vos bundles systèmes, bundles applicatifs et fichiers de config.
  • hostActivator : un activateur OSGi mère en quelque sorte qui permet de faire le pont entre votre classloader tomcat et ceux des bundles. C'est grace à ce bundle que vous allez pouvoir donner de la visibilité aux librairies qui seront embarquées dans votre WAR ou dans votre tomcat vers l'environnement felix.
L'objectif de cette classe est donc de récupérer les singletons qui l'intéressent et de fournir une liste de classe de configuration en leur fournissant les instances dont elles ont besoin.

La classe abstraite qui l'accompagne est quant à elle chargée de mettre en oeuvre le démarrage de felix :


Là, rien de mystérieux, on démarre felix en lui fournissant les éléments de configuration qui lui seront nécessaire. On peut cependant remarquer que toutes les propriétés fournies sont un ensemble de clés-valeur à l'exception du bundle système (HostActivator) dont on fournit directement l'instance.

Le reste n'est que de l’enchevêtrement de bean spring afin de fournir les instances à chacun des protagonistes, felix étant démarrer du fait de l'instanciation par spring de la classe HostedFelix qui contient des annotations @PostConstruct et @PreDestroy sur des méthodes respectivement en charge de démarrer et d'arrêter felix.

Il ne vous reste plus qu'à régler la propriété rootDir afin de définir l'endroit on vous souhaitez déployer vos bundles.

Voilà, si tout va bien vous avez un felix embarqué dans tomcat. Au démarrage, ce dernier devrait vous donner une liste de bundle installé. Ces bundles sont attendus dans le dossier {rootDir}/bundle mais encore une fois, ceci peut être modifié en jouant avec les fichires de config.

Le prochain article décrira comment mettre en place un système d'autodeploy de bundle avec le bundle felix.fileinstall (déjà en partie configuré dans ce tutoriel mais non déployé)

mardi 29 avril 2014

La collecte d'erreur AngularJS


La réalisation de webapp avec javascript soulève de nouveaux problèmes. 
L'un d'entre eux est la collecte des erreurs générée par le code s'exécutant sur le navigateur d'un utilisateur.

Problème

En effet quelque soit la qualité de votre travail, de la quantité de code testé, ou des efforts mis en oeuvre par votre team qualité, un de vos utilisateurs tenteras quelque chose d'inattendu, utiliseras un navigateur exotique, sera bridé par une configuration étrange ou n'importe quoi d'autre qui provoqueras des erreurs sur votre application. 
Or la collecte de ces erreurs est cruciale pour améliorer l'expérience utilisateur. 
Nous savons parfaitement récupérer les logs d'exécution sur nos serveurs ils sont chez nous après tout, enregistrer une date, un message d'erreur, une stacktrace et pourquoi pas quelques informations permettant de définir le contexte de la levé d'erreur n'est pas si compliqué. 
Mais quand l'erreur se produit sur le navigateur d'un client comment faire ?

Le principe

La solution est relativement simple, il s'agit d'intercepter l'erreur et d'envoyer un message enrichis avec des informations permettant de contextualiser celle-ci, à un webservice. 
L'implémentation que je vous propose convient pour AngularJS.

La gestion des erreurs dans AngularJS

Toute Exception levée dans le scope d'exécution d'AngularJS fini invariablement traitée dans le service exceptionHandler.

Le fonctionnement de celui-ci est par défaut de mettre a contribution le service $log et de demander a celui-ci de le traiter comme un message d'erreur $log.error() ce qui a pour effet de générer une erreur dans la console de votre navigateur.

Implémentation

Nous allons créer un module AngularJS qu'on déclarera au bootstraping de l'application et qui contiendra un décorateur du service $log qui nous permettra de surcharger une partie de la méthode error de celui-ci.

Le service $provide nous permet a l'aide de sa méthode decorator d'intercepter l'instanciation du service $log et de modifier ses fonctionnalités.
Il ne nous reste plus qu'as écrire un service permettant d'envoyer les messages a un webservice quelconque. 
Ce service pourrait ressembler à ceci :

J'utilise _.memoize pour éviter de faire de multiples requêtes concernant la même erreur depuis un seul client. C'est particulièrement utile pour éviter de surcharger d'appel Ajax un mobile ou dans le cas d'une erreur se déclenchant dans une loop.

JQuery est utilisé a la place de $http pour effectuer des requetes XMLHttprequest car $http utilise exceptionHandler qui utilise lui même $log.