Applicazione pagina singola angular.js + Python – parte 1

Introduzione

Con la serie di articoli di cui questo è il primo, voglio dettagliare come ho fatto a creare una piccola applicazione web, partendo da zero e utilizzando a scopo autodidattico alcune tecnologie mature ma nuove per me.

L’applicazione è caratterizzata da una separazione piuttosto netta tra backend e frontend; il primo, scritto in Python, fornisce un’interfaccia REST (livello 2 della scala di maturità di Richardson); essenzialmente espone funzionalità CRUD (soprattutto R e poco delle altre) per entità persistite in un DB relazionale MySql; il frontend è un’applicazione a singola pagina Angular.js. Per gli stili ho usato Bootstrap 3.

Oltre al codice intendo descrivere anche gli strumenti usati, come il gestore di macchine virtuali Vagrant e il package manager Javascript Bower.

Dominio del problema

Una piccola premessa per spiegare cosa fa l’applicazione è d’obbligo.

Nell’azienda per cui lavoro utilizziamo Scrum per lo sviluppo del business: come spesso succede i team sono cross-funzionali, sono cioè composti da professionalità differenti.

È nata quindi l’esigenza di riunire tutti i programmatori dell’unità funzionale (i.e. dell’insieme degli scrum team  che più o meno lavorano in comparti adiacenti) in un gruppo simile alle gilde di Spotify.

Questo gruppo è chiamato disciplina e ha molteplici scopi: facilitare lo scambio di conoscenze tra sviluppatori, evitare che i team Scrum si trasformino in compartimenti stagni, favorire la crescita professionale dei singoli sviluppatori eccetera.

La disciplina ha un core team, un gruppo di sviluppatori senior che hanno il compito di coordinare e di impegnarsi in prima persona nelle attività operative. Alla guida c’è il Discipline Owner, che è una specie di Product Owner ma il cui scopo principale è decidere le attività che il core team porta avanti e le loro priorità.

Ogni membro del core team ha anche il titolo di mentor, in quanto uno dei suoi compiti è quello di fare da punto di riferimento per altri colleghi della disciplina che rivestono il ruolo di mentee: il rapporto mentor-mentee è di circa 1 a 4, ogni mentee ha un mentor di riferimento.

Il sottoscritto è uno dei membri del core team.

Recentemente una delle attività da svolgere è stata il censimento dei vari IDE usati, in modo da gestire al meglio le licenze a disposizione, pianificare l’acquisto degli aggiornamenti e decidere in che ambienti preparare i workshop interni che periodicamente vengono svolti (altro compito della disciplina).

Volendo evitare il classico tabellone Excel abbiamo deciso di mettere le informazioni raccolte in un DB relazionale; da qui mi è nata l’idea di realizzare un piccolo gestionale per facilitare le attività di raccolta dati del Core Team.

Entità e relazioni

Il modello è piuttosto semplice. Al momento il database è composto da due sole entità:

  • discipline_member: sviluppatore; caratterizzato da un minimo di anagrafica e il ruolo rivestito (mentor, mentee o ex-membro se persona nel frattempo passata ad altra unità operativa)
  • ide: ambiente di sviluppo; caratterizzato dal nome, dalla versione e da un booleano che indica se è o meno gratis (in caso contrario necessita di una licenza d’uso)

Le relazioni che legano le entità sono:

  • rapporto mentor-mentee
  • il fatto che uno sviluppatore usi un certo ide; in caso l’ide sia a pagamento, questa relazione è caratterizzata dalla licenza d’uso.

Di seguito il diagramma E-R che è stato creato:

Diagramma E-R del DB
Diagramma E-R del DB

Come è facile capire, il diagramma è stato creato con MySQL Workbench a partire dalla tabelle.

Iniziamo

Ho deciso che il codice girerà in locale, non su un server remoto: per facilitare comunque la portabilità dell’applicazione non configurerò la macchina fisica su cui lavoro ma una macchina virtuale.

Introduciamo Vagrant.

Nel mio progetto creo quindi per prima cosa la directory vagrant che conterrà la configurazione e la ricetta per creare la macchina virtuale. Per il provisioning della macchina userò Puppet.

Il contenuto del file Vagrantfile può essere consultato a questo indirizzo. Oltre alle normali direttive (redirect delle porte, condivisione directory) ho aggiunto due provisioning ‘shell’ per l’installazione di puppet (tramite apt) e di alcuni suoi moduli che verranno utilizzati per l’installazione e la configurazione del sistema.

Il grosso del lavoro lo farà poi puppet: il lavoro del provider è diviso in due files: il primo contiene i pacchetti apt da installare che non sono gestiti da moduli puppet appositi o che non sono stato capace di configurare con i moduli appositi (come WSGI). Il secondo file contiene le informazioni più interessanti: fa sì che puppet installi Python con le librerie che verranno utilizzate per sviluppare l’interfaccia REST; crea il database eseguendo uno script che descrive le tabelle e gli indici e l’utente con i grant necessari (in effetti  i grant sono anche troppo permissivi); installa Bower tramite npm (il package manager di nodejs).

La configurazione di Apache non sono riuscito a farla col modulo apposito ma creando il file di configurazione e installando i moduli necessari. Questo comporta che al primo avvio della macchina probabilmente sarà necessario riavviare apache.

Una volta finito sarà possibile lanciare vagrant up e verificare che la macchina virtuale funziona.

Nel prossimo articolo popolerò il Database con dei dati di prova e comincerò a sviluppare il backend in Python.

Codility Nitrogenium award

Once in a while, I like to play with the awesome Codility challenges.

Yesterday evening, after a holiday-class lunch, while deciding what not to have for dinner, I approached the latest challenge, Nitrogenium.

I’m not smart enough, nor trained I guess, to hit the target at the first attempt: I got the algorithm working, but not fast enough since they ask for a ~O(n+m+[something-else-linear]) solution; all I could think of was a O(n*m)-ish algorithm.

I went to bed late, tired and, most notably, frustrated: but a good night of sleep and weird, flooding island haunted dreams brought a working solution, so after a pair of attempts (my fault for not testing limit conditions)…

I got the golden award :-)

Here it is!


def solution(a, b)
  variations = initialize_variations(a, b)
  calculate_variations(a, variations)
  islands_per_level = variations_to_islands_per_level(variations)
  calculate_islands_per_day(b, islands_per_level)
end

=begin
Initializes a vector of 0, long as the max between the maximum water level and the maximum
plot height: should be thought of as an hash having as keys the possible water levels and as values
the increase/decrease of islands number for that level (assuming [-1] = 0)
=end
def initialize_variations(a, b)
  [0] * ([a.max, b.max].max+1)
end

=begin
 Starting from the minimum sea level (0) calculates how many islands are created or flooded for each level
=end
def calculate_variations(a, islands_per_level)
  previous = 0
  a.each do |plot_level|
    if plot_level > previous
      #if the current plot is higher than the previous, it means that the current island is overseas up to the current
      # plot height, so we increase the number of islands for the current level and decrease the number of islands
      # for the previous' plot level (only if previous is greater than 0)
      islands_per_level[plot_level-1] =  islands_per_level[plot_level-1]+1
      islands_per_level[previous-1] = islands_per_level[previous-1]-1 if previous > 0
    end
    previous = plot_level
  end
end

=begin
Starting from the top (where there are 0 islands) calculates how many islands there are for every water level
(returns something like the discrete integral of islands_variations_per_level, from end to start)
=end
def variations_to_islands_per_level(islands_variations)
  current = islands_variations.length - 2
  t = islands_variations[current+1]
  current.downto 0 do |i|
    islands_variations[i] = islands_variations[i] == nil ? t : islands_variations[i]+t
    tmp = islands_variations[i]
    if tmp != 0
      t=tmp
    end
  end
  islands_variations
end

def calculate_islands_per_day(b, islands_per_level)
  islands_per_day = []
  b.each do |level|
    islands_per_day << islands_per_level[level]
  end
  islands_per_day
end

Come configurare un progetto Ruby in JenkinsCI

Introduzione

L’azienda per cui lavoro mi ha concesso per qualche mese un server virtuale, come beta tester.

Ho quindi deciso di mettere un progettino didattico che sto portando avanti per imparare Ruby sotto Continuous Integration: questo mi ha dato modi di entrare in confidenza con un po’ delle tecnologie che ruotano intorno al linguaggio Ruby, nello specifico simplecov, rake e rspec.

L’obiettivo è di avere un progetto su GitHub, su cui un’istanza di Jenkins fa periodicamente build, facendo girare i test unitari e producendo report sull’esito dei test e sulla copertura del codice.

Caveat

La mia conoscenza di Ruby è piuttosto limitata ed ho anche coscientemente evitato di affrontare tutto lo stack di tecnologie. In particolare ho volutamente ignorato gem e Rails; altre cose interessanti potrebbero essermi sfuggite.

Creazione dei files

Per avere i report che ci interessano, sfruttando al massimo Jenkins, bisogna produrre report in formati riconosciuti. Per i test unitari, lo standard de facto è xUnit. Su GitHub ho trovato un Gist con un formatter per rspec che fa proprio al caso mio. Copio e incollo la classe sotto /spec/formatters (da quello che ho capito i Gist non si possono usare in altro modo).

Per quanto riguarda i report sulla copertura, esiste un plugin per Jenkins che riconosce il formato prodotto da rcov, una gemma non più supportata da Ruby 1.9+: fortunatamente simplecov produce report anche in quel formato.

Il layout del mio progetto è quindi questo:

    src/
        class_1.rb
        class_2.rb
    spec/
        class_1_spec.rb
        class_2_spec.rb
        formatters/
            junit.rb
    spec_helper.rb
    Rakefile

Non ho riportato tutte le classi e gli spec relativi per non far troppa confusione.

Partendo dal fondo:

  • Rakefile: Ruby sfrutta rake, che a sua volta necessita di questo file per fare la build. Non avendo particolari esigenze, il contenuto di questo file sarà semplicemente:
    require 'rspec/core/rake_task'
    
    RSpec::Core::RakeTask.new(:spec) do |t|
      # -P dice a rspec dove sono i sorgenti
      # -r fa includere i files indicati prima dell'esecuzione dei test
      # -f specifica il formato del report, che deve essere compatibile con jUnit
      # -o indica il nome del file in cui produrre il report
      t.rspec_opts = '-P src/ -r ./spec_helper.rb -r ./spec/formatters/junit.rb -f JUnit -o results.xml'
    
    end
    task :default => :spec
    
  • La configurazione di simplecov va fatta nel file spec_helper.rb, per motivi che non mi sono ben chiari: probabilmente rake fa spawning di processi figli e inizializzando la copertura del codice nel Rakefile lo si fa nel processo sbagliato.Il file spec_helper.rb ha questo contenuto:
    require 'simplecov'
    require 'simplecov-rcov'
    # forza la generazione del report nel vecchio formato che Jenkins sa leggere
    SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
    SimpleCov.start do
      # esclude i files di test dal computo della copertura
      add_filter('spec/')
    end
    

A questo punto, dopo aver configurato tutte le gemme necessarie, eseguendo da console il comando rack si è in grado si vedere l’esito dei test e la percentuale di copertura. Nella directory ./coverage si trova l’HTML con i report.

Configurazione di Jenkins

L’installazione e la messa in sicurezza di Jenkins sono fuori dall’obiettivo di questo post: si possono trovare molte quide online, ad esempio qui.

I plugin necessari al nostro scopo sono:

A questo punto è tutto davvero molto semplice: si installano i plugin e si riavvia Jenkins.

Si crea quindi un nuovo progetto scegliendo ‘Configura una build di tipo generico‘, i dati da inserire sono:

  • GitHub project: il link del progetto GitHub, ad esempio https://github.com/iacopo-papalini/rubyMIX
  • Gestione del codice sorgente: scegliere ‘Git’ e dare l’URL del master git, ad esempio https://github.com/iacopo-papalini/rubyMIX.git

screen-capture-1

  • Build Triggers: scegliere Build when a change is pushed to GitHub [edit - non sono riuscito a configurare correttamente questo plugin, quindi ho ripiegato su un piùsemplice 'Poll SCM']
  • Build: un unico passo – invoke Rake, selezionare la versione di rake installatascreen-capture-2

Tocco finale

Per raggiungere lo scopo che mi ero prefisso, manca solo la pubblicazione dei report: sempre dalla pagina di configurazione di Jenkins, è necessario aggiungere due Azioni Dopo la Build, nello specifico:

  • Publish JUnit test result report, con parametro results.xml
  • Publish Rcov report, con parametro coverage/rcovConfigurazione plugin reportistica

Una volta finito, lanciando la build a mano e tornando alla home del progetto, si dovrebbe vedere una cosa del genere:

La copertura è _davvero_ ~100%
La copertura è _davvero_ ~100%

I miei Kata: Dice Roller

Uno

Visto che mi sono messo in testa di imparare Ruby, continuo a risolvere piccoli problemi per prendere confidenza con la sintassi e gli strumenti.

Ho deciso di creare un mio nuovo kata, per capire come fare mock (o test doubles) in Ruby.

Due

Ho chiamato il kata ‘Dice roller': si tratta di una classe che, data un’espressione che rappresenta un tiro di dadi nel sistema di gioco di ruolo d20 system, simula il lancio di dadi corrispondente. Ad esempio:

  • 1d6 – tira un dado a sei facce
  • 2d8+3 – tira due volte un dado ad otto facce ed aggiunge 3 alla somma dei lanci

Il modificatore è opzionale e può anche essere negativo, ma il risultato di una serie di tiri non potrà mai essere meno di 0 (ciò significa che 1d4-5 darà sempre 0).

Tre

Questo kata offre come difficoltà quella di testare l’uso di funzioni che restituiscono valori casuali. Dal momento che intendo usare la libreria di sistema, la verifica da fare in fase di test non è tanto che i risultati siano casuali e distribuiti in maniera corretta (per questo mi fido di Ruby), quanto che la funzione di sistema rand sia chiamata correttamente.

Entra in scena RSpec: si tratta di un tool nato per il BDD, ma che si comporta benissimo anche come supporto al TDD.

Utilizzando i moduli mock e expectations, il codice dei test diventa molto discorsivo, simile alla ‘well-written prose’ che dovrebbe caratterizzare il codice pulito.

Rispetto ad altri linguaggi, in Ruby risulta possibile rimpiazzare con dei mock anche le primitive di libreria, ad esempio:

  before(:each) do
    Kernel.stub(:rand)
  end

è sufficiente per rimpiazzare tutte le chiamate alla funzione rand con un mock, su cui poter fare asserzioni.

Il risultato del kata è su GitHub

Ruby thursday

Uno

Inizio l’ultima parte dell’anno riprendendo il proposito, consigliato nel buon ‘The Pragmatic Programmer‘, di imparare un nuovo linguaggio di programmazione all’anno, visto che sono ancora in tempo.

Ho scelto Ruby (senza Rails) senza un motivo particolare, diciamo che non mi sento ancora pronto ad ingoiare il boccone dei linguaggi funzionali.

Visto il poco tempo non mi sono messo a studiare le basi del linguaggio ma, munito di un buon IDE e di Google, mi sono messo alla ricerca di kata per prendere confidenza risolvendo (facili) problemi che ho trovato su codersdojo.org.

Due

Ho cominciato col classico ‘Numeri Romani': dato un intero positivo non troppo grande scriverlo in numerazione romana.
Devo dire che l’aiuto di un buon IDE si sente: non solo l’autocompletamento dei campi, ma anche l’evidenziazione degli errori è molto utile durante l’apprendimento di un nuovo linguaggio.

Ho poi affrontato il kata dell’ “orologio berlinese”, giusto perché ormai avevo un ritmo abbastanza buono.
Ho salvato i risultati su GitHub.

Tre

Le prime impressioni del linguaggio sono miste: da una parte sembra avere molti degli aspetti positivi dei linguaggi compilati, un po’ come Python si tratta comunque di un linguaggio fortemente tipato e la sintassi è espressiva.
Dall’altra parte, sento puzza di PERL, nel senso che come novizio trovo che ci siano fin troppi modi di eseguire operazioni banali, come l’inizializzazione di un array o la concatenazione di stringhe.

Per giungere ad un giudizio più convinto, dovrei provare a risolvere un problema complesso, magari provando a lavorare su codice altrui.

Yet another attempt at not losing my experiments