Migrazioni con ActiveJDBC

Era una cosa che volevo fare da un po', perché con ActiveRecord in Ruby si fa abbastanza facilmente, ma ActiveJDBC, pur comodissimo, non permette di fare il provisioning del database all'avvio dell'applicativo. C'è un plugin che permette di farlo via Maven, ma sono piuttosto contrario all'usare Maven in giro sulle macchine di produzione e ogni applicativo ha diverse produzioni differenti. La soluzione ideale per il mio caso d'uso sarebbe di avere il tutto che gira ogni volta che parte l'applicativo, sia per mettere a posto eventuali aggiornamenti, sia per i nuovi deploy.

Dopo qualche mese di tentativi quando avevo tempo, ho finalmente trovato un workaround che funzioni (l'alternativa era farmi il tutto io parlando direttamente con JDBC, ma mi dava fastidio concettualmente l'idea di dover passare a uno strato inferiore solo per questa cosa, dovendomi smazzare io tutte le connessioni e gli errori). Per comodità mia e perché non si sa mai che possa servire a qualcun altro, riporto qua tutto il processo.

Il primo step è di aggiungere il plugin come dipendenza del codice (purtroppo non c'è un pacchetto a parte), quindi prendete il vostro pom.xml e aggiungete la seguente dipendenza:

 <dependency>
        <groupId>org.javalite</groupId>
        <artifactId>db-migrator-maven-plugin</artifactId>
        <version>${activejdbc.version}</version>
</dependency>

(Io uso la variabile activejdbc.version per tenere sincronizzate tutte le versioni della roba di ActiveJDBC che uso)

A questo punto, all'avvio del vostro applicativo (o nel momento in cui vi serve di più)

import org.javalite.db_migrator.MigrationManager;
import static org.javalite.db_migrator.DbUtils.*;
...
try{
    openConnection(driver, databaseUrl, databaseUser, databasePassword);
    MigrationManager migrator = new 
    MigrationManager("/path/to/migration/folder");
    migrator.migrate(logger, "utf-8");
}finally{
    closeConnection();
}

Inizialmente avevo trovato dei metodi statici per ottenere driver, databaseUrl, databaseUser e databasePassword, ma sono pensati per i test e ritornano dei valori fissi. Siccome database.properties (il file che usa ActiveJDBC per configurare la connessione al DB) deve essere comunque presente ed è piuttosto facile da leggere (io uso Config di TypeSafe), mi sono limitato a prenderlo su così e ad estrarre i dati.

Tenete conto che dovrete gestire delle eccezioni di tipo SQLException. Per sicurezza, io faccio fallire e fermare il processo in caso di eccezioni, perché se ci sono problemi di DB non ha senso che parta nient'altro e voglio un bell'alert grosso e inquietante che mi avvisi appena schiaccio il bottone “deploy”.

Un'unica precauzione che mi ha fatto bestemmiare per un po': MigrationManager, essendo pensata per un plugin Maven, si aspetta di ricevere un logger del tipo org.apache.maven.plugin.logging.Log, che tipicamente non usate in un applicativo. Io ho risolto creandomi una classe wrapper che rimappa le funzioni di Log sul logger che uso normalmente (io preferisco SLF4J). Se vi appoggiate a un IDE ci vuole relativamente poco, sfruttando l'autocompletamento, a risolvere il tutto.

L'ultimo step, siccome uso Docker, è di piazzare dentro il Dockerfile questa riga:

COPY src/migrations ./migrations

Dove src/migrations è il path che il plugin di ActiveJDBC usa per salvare le migrazioni (e alla fine io uso quello per generarle).