Automatiser les changements de schéma de base de données avec Flyway

5 juillet 2011 1 commentaire

J’ai été intrigué par le système de mise à jour de base de données de l’appliation Sonar. Quand vous installez une nouvelle version, l’application vous indique que votre base de données est en version x et qu’il faut la migrer en version y. Un clic et un peu de patience plus tard, la mise à jour de la base de données est effective.

J’ai également rencontré le même système de mise à jour pour le portail projet Redmine. Le point commun entre ces deux outils : ils sont basés sur Ruby On Rails, le fameux framework intégré de développement web basé sur le langage Ruby.

Ruby On Rail intègre un outil nommé Migrations qui permet en effet de spécifier des migrations unitaires de base de données, le framework permettant de les appliquer de manière incrémentale.

En Java, les outils de mapping objet/relationnel disposent de fonctions similaires. Par exemple Hibernate sait générer le modèle de base de donnée (DDL) qui correspond à vos entités JPA . Il sait également mettre à jour une base de données pour lui ajouter des éléments (propriété hibernate.hbm2ddl.auto). Dans de nombreux cas c’est suffisant. Cependant certains cas ne sont pas gérés, par exemple le changement de type d’une colonne ou la suppression de colonnes.

J’ai récemment rencontré un cas ou nous avions besoin d’aller plus loin, en effet les applications traversent de nombreux environnements de qualification avant d’arriver en production. Dans ce cas les opérations manuelles sont à éviter le plus possible. J’ai donc recherché un outil équivalent à Migrations mais en Java.

J’ai trouvé mon bonheur avec Flyway, une librairie OpenSource (License Apache 2.0) qui permet d’automatiser les changements de schéma. L’idée est simple :

  • Flyway stocke dans une table (SCHEMA_VERSION) un journal des scripts déja executés sur cette base de données
  • L’application embarque les scripts SQL suivant une convention de nommage
  • Au démarrage de l’application, Flyway calcule l’écart entre la table SCHEMA_VERSION et les scripts présents dans votre application et applique les nouveaux scripts
De plus il est possible d’écrire les scripts en SQL mais aussi de les écrire en Java pour les opérations plus complexes. Par exemple pour adapter les données au nouveau schéma.
Pour intégrer flyway dans votre application, il est nécessaire d’ajouter la librairie flyway-core qui s’appuie sur spring-jdbc.
Pour les utilisateurs de maven, ajouter cette dépendance dans votre pom :
<!-- Migrations de schémas SQL -->
<dependency>
  <groupId>com.googlecode.flyway</groupId>
  <artifactId>flyway-core</artifactId>
  <version>1.4</version>
</dependency>

Les fichiers SQL doivent se trouver dans le classpath de l’application à l’exécution, dans un package db.migration. Les utilisateurs de maven les placeront donc dans src/main/resources/db/migration . Les fichiers doivent être nommés sur le pattern suivant : VX_yyy_zzz.sql, X représentant le numéro de version et yyy_zzz étant un libellé de version. Par exemple : V1_initialize.sql ou V6_parameter_table.sql .

Pour lancer la migration, une API est disponible. Toutes les opérations sont disponibles dans la classe Flyway, voici un exemple d’utilisation :

Flyway flyway = new Flyway();
flyway.setDataSource(dataSource);
flyway.migrate();

L’intégration avec Spring est soignée, il suffit de déclarer le bean avec une méthode d’initialisation

<bean id="flyway" class="com.googlecode.flyway.core.Flyway" init-method="migrate">
  <property name="dataSource" ref="dataSource"/>
</bean>
Pour écrire un script de migration en Java, la encore il faut suivre une convention de nommage. Les classes  doivent se trouver dans le package db.migration et suivre le même pattern de nommage que les scripts. Par exemple : V4_settings_migration.java. Les classes doivent également implémenter l’interface JavaMigration.
Par exemple :
package db.migration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;

import com.googlecode.flyway.core.migration.java.JavaMigration;

public class V54_sample_migration implements JavaMigration {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Override
	public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
		logger.info("V54_sample_migration STARTING");
		int countOfActorsNamedJoe = jdbcTemplate.queryForInt("select count(0) from actors where first_name = ?", "Joe");
		jdbcTemplate.update("update stats set count = ? where first_name = ?", countOfActorsNamedJoe, "Joe");
		logger.info("V54_sample_migration ENDED");
	}
}

Toutes les conventions de nommage sont configurables (package de base, préfixe et suffixe des scripts, …) . Pour assurer les évolutions d’un modèle de données existant, il est nécessaire d’ajuster la propriété disableInitCheck à true dans le cas contraire Flyway déclenche une exception.

Bref un outil simple et extrêmement pratique pour rendre le déploiement de vos applications un peu plus fluide.

Publicités
Étiquettes : ,

HTTP sans session : pourquoi et comment ?

Voici une présentation rapide sur un sujet assez populaire en ce moment : Les applications Web sans session coté serveur d’application.

J’ai écrit cette présentation avec mon collègue Guillaume, nous y revenons sur les principes basiques de HTTP, à quoi sert une session coté serveur et comment s’en passer.

Étiquettes : ,

Subversion 1.7 bientôt dans les bacs

La version 1.7 de Subversion va prochainement être publiée. Sans gommer tous les écarts avec les outils de gestions de source décentralisés comme Git ou Mercurial, cette version de subversion apporte des améliorations sympathiques pour les développeurs dont l’organisation reste fidèle à Subversion

  • Fin des répertoires « .svn » à tous les étages.

Et oui, combien de fois avez vous pesté contre cette kyrielle de répertoires « .svn » que subversion semait dans chaque répertoire, sur ces exports de sources qu’il fallait filtrer, sur la copie d’un répertoire qui se trouvait dans un état instable si il n’avait pas été fait avec le « svn mv » ou avec le plugin qui va bien dans votre IDE ? A partir de la version 1.7, Subversion n’utilisera qu’un seul répertoire .svn à la racine de la working copy.

Contrairement aux versions précédentes, le client Subversion 1.7 ne mettra pas à niveau automatiquement la structure des working copy, il faudra exécuter la commande « svn upgrade » pour mettre à jour la working copy.

A noter que ce nouveau répertoire « .svn » contient une base de données SQLite, l’équipe Subversion déconseille d’accéder directement à cette base de données. Il est également déconseillé de copier une working copy qui est cours de modification par le client svn : la base de donnée copiée risque d’être inutilisable.

  • Meilleur dialogue client/serveur en HTTP

Subversion abandonne le protocole WebDAV Delta-V pour optimiser le dialogue client/serveur. Attention, pour profiter de ces optimisations, il faut que le client et le serveur soient en version 1.7.

  • Première version Apache Foundation

Subersion 1.7 est la première version herbergée par la fondation Apache, cela implique un léger changement de licence et surtout, les binaires seront disponibles directement sur le site du projet et non plus chez des tiers.

Cette version 1.7 intègre de nombreuses autres évolutions et corrections, je vous conseille de lire la page dédiée

Étiquettes :

CloudBees au Chti’JUG chez norsys le 28 Juin 2011

Norsys accueille pour la seconde fois le Chti’JUG dans ces locaux à l’occasion de la session consacrée à CloudBees. Ce sera l’occasion d’en savoir plus sur les offres CloudBees DEV@Cloud et RUN@Cloud.

DEV@Cloud est une offre d’hébergement des sources et d’intégration continue à l’aide de Jenkins. Kohsuke Kawaguchi le papa de Jenkins travaille d’ailleurs pour CloudBees.

RUN@Cloud est une plateforme élastique d’hébergement Java EE (Tomcat + MySQL).

Cette session aura lieu à l’occasion du 7eme campus d’été norsys, un temps fort de formations et de conférences

Rendez vous le 28 Juin !

Inscriptions ici.

 

 

Étiquettes :

Un product owner qui code

Mon année 2010 a été marquée par un projet très enthousiasmant à plusieurs titres :

  • Un projet innovant : une expérimentation autour de la mobilité menée par 14 enseignes de la grande distribution.
  • Une prise en charge complète : nous prenions en charge la totalité du projet, de la conception jusqu’à l’hébergement en passant par le support aux utilisateurs.
  • Une très forte autonomie : On m’a réellement confié  les clefs du camion, tant en management d’équipe que techniquement.

C’est sur ce dernier point que je vais m’étendre, en effet je suis convaincu depuis plusieurs années par les méthodes agiles et c’était l’occasion de mettre en oeuvre ces convictions sur un gros projet.

Nous avons commencé le projet avec une équipe réduite afin de prototyper différentes solutions et de les proposer aux clients, je tenais à ce moment deux rôles sur le projet : Architecte/Développeur sénior et ScrumMaster. Les sprints étaient très courts (une semaine) et notre backlog ne nous donnait pas beaucoup de visibilité.

Une fois les contours fonctionnels et l’architecture globale de la solution définis, nous sommes entrés réellement dans une phase de production avec deux releases : tout d’abord le backoffice, l’application iPhone et le site Web et ensuite l’application Android et le site web mobile.

Comme nous avions de multiples clients, je me suis beaucoup investi dans la construction de la backlog de produit. J’ai alors constaté  que je cumulais décidément trop de rôles et que je délaissais l’animation de l’équipe. J’ai alors profité de l’arrivée dans l’équipe de mon ami Jérémy pour lui confier le rôle de ScrumMaster. Il arrivait plutôt dans le rôle d’un équipier (très) confirmé mais il était investi dans les méthodes agiles depuis plusieurs années et avait suivi la formation de Juff Sutherland plusieurs mois plus tôt.

Jérémy a ainsi consacré plus d’énergie que moi à l’animation de l’équipe et surtout aux rétrospectives ce qui c’est montré très efficace.

Nous avons donc conduit à bien le projet avec cette organisation un peu particulière :

  • Un directeur produit (Product Owner) / Architecte
  • Un Scrum Master / Architecte

C’était mon premier projet au sein duquel le directeur produit participe à la production du produit, cela peut paraitre un peu particulier mais notre produit était constitué de quatre applications basées sur des web services communs, il est important que le directeur produit ait une vue précise des rôles de chaque composant.

Ce projet m’a donc permis de mieux comprendre le rôle de directeur produit, je peux donc vous confirmer que ce rôle est particulièrement difficile, rédiger une backlog utilisable et cohèrente est un exercice particulièrement pointu.

Ce projet fut une expérience extraordinaire dans laquelle je me suis investi à fond et dont je suis particulièrement fier. Effet de bord, je n’ai publié qu’un malheureux article en 2010, mais (bonnes résolutions oblige) je vais essayer de partager avec vous ce que j’ai appris à travers ce blog.

Étiquettes :

WebService "Contract First" avec Spring-WS

Contract First vs Code First

La méthode la plus répandue pour exposer des WebServices en Java est la méthode dite « Code First » : on développe un composant Java et on s’appuie ensuite sur un outil pour exposer ce composant sous forme de WebService. Cet outil va analyser les signatures des méthodes de votre composant et va générer un descripteur de WebService : le WSDL. Le développeur jette ensuite un oeil (ou pas) à ce descripteur et se félicite de ne pas avoir eu à écrire ce fichier très verbeux.

Un autre développeur récupère le fameux descripteur WSDL et utilise un autre langage ou un autre outil pour générer le code client et c’est la que les choses se compliquent souvent.

Je vais prendre comme exemple un service de recherche dans un annuaire, ce service permet de rechercher des personnes à partir de leur nom, prénom et ville de résidence. Deux critères sont obligatoires : le nom et la ville de résidence.

Voici l’interface Java qui correspond au service :

@WebService
public interface AnnuaireService {
	List<Personne> rechercher(String nom, String prenom, String ville);
}

Une fois ce service déployé à l’aide de la pile de WebService CXF, le WSDL généré déclare le type suivant pour représenter le message de recherche :

<xs:complexType name="rechercher">
	<xs:sequence>
		<xs:element minOccurs="0" name="arg0" type="xs:string" />
		<xs:element minOccurs="0" name="arg1" type="xs:string" />
		<xs:element minOccurs="0" name="arg2" type="xs:string" />
	</xs:sequence>
</xs:complexType>

Cette déclaration n’est pas utilisable dans l’état, les critères de recherche ne sont pas nommés et tous les critères sont marqués comme optionnels (minOccurs=0).
Il est donc necessaire d’annoter l’interface Java pour permettre à CXF de générer un WSDL plus significatif.

@WebService
public interface AnnuaireService {
	List<Personne> rechercher(
		@WebParam(name="nom") String nom,
		@WebParam(name="prenom") String prenom,
		@WebParam(name="ville") String ville);
}

Les critères de recherche sont maintenant nommés dans le WSDL :

<xs:complexType name="rechercher">
	<xs:sequence>
		<xs:element minOccurs="0" name="nom" type="xs:string" />
		<xs:element minOccurs="0" name="prenom" type="xs:string" />
		<xs:element minOccurs="0" name="ville" type="xs:string" />
	</xs:sequence>
</xs:complexType>

La spécification JAX-WS ne permet pas de rendre les paramètres obligatoires.

Il existe bien sur des solutions de contournement pour que le WSDL soit plus conforme au contrat du service, mais cet exemple trivial nous montre que la génération du WSDL à partir du code Java necessite d’enrichir le code avec des méta données et que le résultat ne permet pas d’utiliser le WSDL généré comme contrat solide entre le producteur et les consommateurs.

Pour que le WSDL reprenne son rôle de contrat, il est important qu’il spécifie le plus précisément possible les messages d’entrée et de sortie du WebService. La démarche « Contract First » met justement l’accent sur la contractualisation des messages échangés.

Contract First avec Spring WS

Le descripteur WSDL reste très verbeux et la perspective de gérer ces fichiers à la main est assez effrayante. Rassurez vous, nous allons nous contenter de décrire les messages en entrée et sortie à l’aide de XML Schema (XSD).
Une fois ces messages décrits, nous allons nous appuyer sur JAXB et Spring-WS pour implémenter le contrat.

Voici le schéma décrivant le message de recherche dans l’annuaire :

<element name="rechercherRequest">
	<complexType>
		<annotation>
			<documentation>
				Message d'entrée pour la recherche dans l'annuaire
   		</documentation>
		</annotation>
		<sequence>
			<element name="nom" type="string" />
			<element name="prenom" type="string" minOccurs="0" />
			<element name="ville" type="string" />
		</sequence>
	</complexType>
</element>

Une configuration rapide de la génération des objets Java à partir du XML Schema à l’aide de maven 2 :

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>jaxb2-maven-plugin</artifactId>
	<version>1.3</version>
	<executions>
		<execution>
			<goals>
				<goal>xjc</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Et voici l’objet qui correspond à la requête :

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "nom",
    "prenom",
    "ville"
})
@XmlRootElement(name = "rechercherRequest")
public class RechercherRequest {

    @XmlElement(required = true)
    protected String nom;
    protected String prenom;
    @XmlElement(required = true)
    protected String ville;
 ....
 

Certes c’est verbeux mais c’est du codé généré donc pas de couts d’intégration ou de maintenance.

Voici maintenant la signature du composant Java qui prend en charge la requête :

@Endpoint
public class AnnuaireEndpoint {
	@PayloadRoot(localPart = "rechercherRequest", namespace = "http://yellowpages.tartachuc.org")
	public RechercherResponse rechercher(RechercherRequest request) {

....

Une première annotation @Endpoint marque la classe et la rend detectable par le « component-scan » de spring. La seconde annotation @PayloadRoot indique quel message est traité par la méthode.

Cette mise en oeuvre de Spring-WS permet d’assurer un contrat WSDL soigné tout en ne pénalisant pas la productivité des développements. Dernier avantage, cette démarche impose de concevoir les messages échangés ce qui favorisera des messages plus expressifs et simples  qui facilitent l’utilisation des services tout en évitant d’exposer directement les objets « privés ».

Étiquettes : ,

Je tweet aussi

Je me suis rendu compte que j’avais oublié d’indiquer ici que j’utilise Twitter notamment pour partager ma veille techno.
Vous pouvez suivre tout cela sur mon compte twitter. J’ai également ajouté mes derniers Tweet dans le bandeau de droite.

Je fais plein de truc intéressants en ce moment mais je n’ai pas le temps  ne prends pas assez le temps de vous en parler ici.

Bonnes fêtes 🙂