Mondi su mondi, sistemi di sistemi.

Utility del giorno: localghost

Thursday, May 27th, 2010

Attraverso @rentzsch ho scoperto Localghost, un programmino per configurare al volo /etc/hosts, per definire quali dominii puntano al 127.0.0.1

Basta lanciarlo, definire le voci DNS e il gioco è fatto. Una volta create, le voci pos­sono essere attivate e disattivate a piacimento.

Cloudkit

Wednesday, April 7th, 2010

L’uso di Ajax rende desiderabile, se non neces­sario, il poter mettere in piedi un server REST in tempi rapidi.

In questo post mi sono imbattuto in CloudKit, un’applicazione in Ruby che permettere di metterne in piedi uno in un batter d’occhio, agganciandoci pure un minimo di backend, in questo caso Tokyo Cabinet.

L’installazione è semplicis­sima e l’unica cosa che ho cambiato rispetto alla ricetta originale è che ho usato MacPorts per Tokyo Cabinet al posto di compilarlo da zero.

Una volta che Cloudkit è attivo, pos­siamo subito cominciare a progettare l’API che il nostro servizio REST esporrà verso l’esterno, magari con un approccio TDD usando una delle innumerevoli librerie in JavaScript per i test1.

Quando poi le cose saranno più stabili potremo sostituire Cloudkit con un backend più adatto, senza toccare una riga nella parte Ajax.

Strumenti come Cloudkit sono vitali per raccordare le diverse fasi di sviluppo perché, anche se abbiamo familiarità con backend più strutturati e siamo in grado di attivarli con facilità, spesso non sono sufficientemente agili per aiutarci nelle fasi fluide dello sviluppo, dove le cose cambiano rapidis­simamente ed è inutile essere troppo rigorosi.

Coda & Subversion

Thursday, March 25th, 2010

Uso Coda ormai da qualche tempo. È un buon programma per lo sviluppo dei siti, piacevole da usare e affidabile, non ho avuto bisogno di molto altro per diverso tempo.

I limiti più evidenti di Coda

Dopo qualche tempo, però, ho cominciato a sentire la mancanza di Emacs e così ho configurato Coda in modo da usarlo come editor esterno. A quel punto ho scoperto con fastidio che la lista dei file da caricare sul sito non veniva sempre aggiornata con le modifiche da Emacs, nonostante fos­sero correttamente individuati come file modificati rispetto alla repository nel VCS (Version Control Software; attualmente è Subversion), costringendomi ogni volta a marcarli a mano. No fun.

Inoltre non sempre abbiamo a che fare con un deployment diretto. Spesso dobbiamo prima caricare il sito su un server di staging e poi, verificato che tutto vada bene, pas­sarlo in produzione. Teoricamente potremmo “ripuntare” Coda fra i due server ma, in pratica, sarebbe un delirio.

Sfruttiamo Subversion!

Il modo ovvio di risolvere questi problemi è usare i punti di aggancio che Subversion mette a disposizione, ovvero, la pos­sibilità di eseguire delle operazioni arbitrarie in concomitanza di alcuni eventi:

  • post-commit
  • post-lock
  • post-revprop-change
  • post-unlock
  • pre-commit
  • pre-lock
  • pre-revprop-change
  • pre-unlock
  • start-commit

In questo caso, lo script agganciato sarà agganciato in post-commit ed effettuerà il deployment.

I dettagli importanti

Una cosa piuttosto semplice, insomma, anche se ci sono diverse cosine di cui tener conto. In primo luogo, è sempre pre­feribile disimpegnare Subversion il prima pos­sibile1 e quindi dobbiamo posporre le varie operazioni; inoltre, dato che lo script è condiviso per tutta la repository, dobbiamo avere l’accortezza di fare le azioni giuste per il progetto giusto; infine, visto che ogni progetto fa storia a sé, dobbiamo trovare un buona fles­sibilità2 per poter specificare le azioni che vogliamo, nel modo che riteniamo più opportuno per quel progetto.

Disimpegnare velocemente la repository

Ad ogni commit viene creato uno script di shell (a partire da un template descritto più avanti) chiamato con il numero di release. La creazione è praticamente istantanea.

Questo script va a finire in una specie di coda sul filesystem. Il fatto di usare una directory invece di un file singolo evita problemi di accesso contemporaneo, lasciando completa libertà nel suo contenuto e mi consente di sfruttare launchd per fare in modo che il proces­samento degli script nella coda venga lanciato solo al momento opportuno. Questo è il file di configurazione:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>it.refactor.deployment-queue</string>
<key>OnDemand</key>
<true/>
<key>Program</key>
<string>/usr/local/bin/projects-auto-deployment</string>
<key>QueueDirectories</key>
<array>
<string>/var/tmp/deployment-queue</string>
</array>
</dict>
</plist>

Il dettaglio importante è la direttiva QueueDirectories che, a differenza di WatchPaths, lancia il processo alla modifica del contenuto della directory, ma solo quando in pre­cedenza era vuota.

In alternativa avremmo potuto usare un qualche daemon. Non un gran problema ma mi sembrava un po’ meno robusto.

Progetti diversi, copioni diversi

Come dicevo prima, lo scopo di usare degli script di shell è quello di lasciare la mas­sima libertà.

Ogni voce della configurazione letta dallo script è simile a questa:

    <regexp> {
        env {
            REL=$rev
        }
        preflight {...}
        deploy {...}
        postflight {...}
    }

Le quattro voci: env, preflight, deploy e postflight raggruppano e ordinano i diversi frammenti che vanno a comporre lo script di deployment, che viene attivato in base all’eventuale match dell’espressione regolare.

L’unica variabile conosciuta è il numero della release, che è pas­sato da Subversion, il resto è tutto libero. Più generico di così!

Di solito, dopo aver definito le variabili ambientali in env, il preflight si occupa di fare il checkout dal VCS; il deploy fa quello che si può immaginare; il postflight si occupa di fare un po’ di pulizia ecc. Ribadisco però il fatto che quelle descritte prima sono solo sezioni: se per assurdo voglio mettere tutto nella sezione env lo posso fare.

Ma non è finita qui…

Tutto bene? Quasi. Così com’è pensato, il sistema non permette di fare alcune cose molto importanti.

Percorsi variabili

Se vogliamo usarlo anche per il deployment delle release “taggate”3, dobbiamo agganciare lo script al percorso dei tag. Tuttavia, dato che l’url completa cambia ad ogni nuovo tag — ad esempio avremo …/tags/REL1, …/tags/REL2 — come facciamo a sapere in post-commit quale prendere?

Per ora non ho trovato una soluzione generica. Mi sono affidato al fatto che il nome della versione in tags è uguale al numero pre­cedente della release di post-commit4. Ad esempio, se l’operazione è stata eseguita alla release 3753, cercheremo la directory r3752. Una volta ottenuta l’URL rientriamo nel caso standard.

Azioni specifiche per le singole release

Un’altra cosa abbastanza frequente è l’esecuzione di operazioni specifiche per una data versione. Esempio tipico: delle modifiche allo schema del database, che vanno fatte una volta sola e non devono essere riapplicate.

Per risolvere questo problema, all’interno del progetto, in una directory nota, creiamo uno script che verrà cercato dal sistema di deployment per essere eseguito5.

roll-up delle modifiche

Infine, nello scenario staging & deployment è desiderabile che una serie di operazioni fatte in staging pos­sano venire applicate in una volta sola in deployment.

Dato che lo script specifico per una data release è anch’esso sotto controllo di versione, abbiamo accesso a tutta la sua storia. Bisognerà andare a prendere quella porzione di modifiche intercorse fra l’ultimo deployment e quello attuale e appenderle ad un unico file, con un comando del genere, ad esempio:
svn log -r3778:3783 -q URL | egrep ^r | awk '{ print $1 }' | sort | while read r; do svn cat -$r URL; done

Come ottengo il range da pas­sare al comando appena visto? Partiamo da un scenario semplificato all’osso, supponendo di partire da una repository nuova di zecca.

Dalla r1 alla r10, lavoro solo nel trunk; quando creo un nuovo tag, in base alle convenzioni adottate prima, il suo nome sarà r10 e il suo numero di release sarà r11. Proseguendo, dalla r12 alla r20, lavoro sempre nel trunk; come prima, creando un nuovo tag, il nome sarà r20 e il suo numero di release sarà r21.

A questo punto, se cerco tutte le modifiche in tags, escludendo quella relativa alla creazione della directory tags, avrò r11 ed r21. Come prima, in base al numero di release pas­sato in post-commit, che al secondo tag sarà 20, posso ricavare il range da considerare, calcolabile come (release immediatamente precedente):(numero di release - 1), ovvero, r11:r19.

There’s more than one way to do it

Per chi non avesse voglia di reinventare la ruota, ci sono delle alternative. In particolare, una delle pos­sibilità che ho preso in considerazione è quella di installare il software di VCS direttamente sul server.

In effetti è una soluzione piuttosto comoda, dato che basta fare un svn up per aggiornare l’installazione e con VCS distribuiti le pos­sibilità sono ancora più ampie.

Ci sono però diversi punti dolenti. Come visto prima è raro che basti un semplice aggiornamento del codice; in secondo luogo non mi piace moltis­simo l’idea che si crei un’ulteriore dipendenza fra il sistema che pubblica gli aggiornamenti e quello che li riceve.

Tolta questa pos­sibilità, ci sono i software di SCM veri e propri, da Puppet a Capistrano, pas­sando per Fabric. Il primo propone un modello in pull, dove i cliente contattano il server per stabilire cosa c’è da fare; il secondo e il terzo sono più vicini ai sistemi in push, dove l’azione è iniziata dal server.

Infine, non dimentichiamoci di Hudson che, pur essendo un server di integrazione, può automatizzare praticamente qualsiasi cosa e quindi anche processi come questi.

Hudson è quello più adatto, essendo un vero e proprio server di continous integration. Anzi, non escludo di cominciare ad usarlo a breve, ma per ora mi bastano le due righe che ho scritto in Tcl.

Per quanto riguarda gli SCM, più vicini al mondo dei sysadmin, Puppet mi piace di più, anche perché ci ho giocato un paio di volte, mentre ho visto usare Capistrano in situazioni simili a quelle descritte.

Come si può notare, alcune delle parti più contorte sono dovute a Subversion. Dato che ho in progetto di pas­sare a Git, sono curioso di sapere se e come il procedimento si semplificherà.

  1. In post-commit Subversion ignora l’esito delle operazioni, dato che il commit è già stato fatto; tuttavia non mi è chiaro se lo esegua in asincrono
  2. avete mai sentito di un progetto che non deve essere fles­sibile?
  3. Per chi non lo sapesse, un tag è un’istantanea del codice sorgente ad un dato momento. In Subversion, è di prassi che le versioni ufficiali stiano in tags e le versioni di sviluppo che deviano da quella principale stiano in branches.
  4. l’uso dei numeri di release come nome del tag non mi risulta sia la cosa più consigliabile, lo so, tuttavia nelle applicazioni con rilasci continui la cosa non mi sembra così grave
  5. Qualcuno storcerà il naso nel vedere una commistione fra deployment e sviluppo, all’interno del sorgente. Non è sicuramente la soluzione più pulita anche se ha l’indubbio vantaggio di tenere sotto controllo di versione anche cose che spesso vengono lasciate un po’ a se stesse. Ad oggi credo che il bilancio costi/benefici sia positivo.

Cosa ci insegna Duke Nukem

Wednesday, January 20th, 2010

Non riesco a smettere di pensare a questo articolo su Duke Nukem Forever. La storia è semplice: dopo il successo planetario di Duke Nukem 3D, rilasciato nel gennaio del 1996, nel 19971 la 3D Realms annuncia l’inizio dello sviluppo del seguito (Duke Nukem Forever, appunto), che non verrà mai terminato.

Il treno passa una volta sola, se passa…

Com’è potuto succedere che un videogioco sia stato sviluppato per dodici anni senza arrivare a un risultato concreto? In poche parole, è stata la spinta maniacale alla ricerca della perfezione.

Non era mai il momento adatto per tirare le fila, rifinire il gioco e farlo uscire. C’era sempre qualcosa da fare ancora, qualche nuova soluzione da provare, un nuovo limite da superare. E in un mondo come quello dei videogiochi c’è una novità ogni santo giorno.

Questa storia l’ho già sentita…

Il racconto è avvincente, eppure, man mano che procede, non si distingue molto da altre storie, questa volta di successo, anch’esse piene di decisioni rischiose. Qual è dunque la differenza?

Ad esempio, a posteriori è facile pensare che solo un pazzo cambierebbe il motore di rendering tre volte ma ho l’impressione che siamo condizionati dall’esito della vicenda. Ogni cosa si spiega quindi a partire dal fallimento, ma è tutto da dimostrare quanto questa o quella decisione abbiano influito negativamente.

È ovvio che una cosa come il cambio del motore 3D comporterà dei ritardi ma se il videogioco fosse uscito probabilmente ammireremmo il coraggio della decisione.

Forse la verità è che la differenza fra un fallimento epocale come questo e il più clamoroso dei successi è meno di quanto pensiamo2. Per non parlare del fatto che tendiamo a dimenticarci quanti siano i fallimenti rispetto ai successi3.

Troppa libertà?

Si riconferma ancora una volta, invece, che la mancanza di limiti è un nemico formidabile. I vincoli sono fondamentali per dare forma e direzione a un progetto.

Credo ci voglia un talento particolare per produrre qualcosa in quelle condizioni: se ce l’hai può venir fuori qualcosa di grandioso ma è meglio non contarci. È un talento che consiste nel lavorare senza bus­sola, facendo affidamento su una qualche forma di senso interiore o forse, semplicemente, è la capacità di auto-imporsi dei paletti da non oltrepassare.

Alla fine…

Alla fine, nel maggio del 2009 Broussard e Miller, i proprietari della 3D Realms, hanno staccato la spina, sciogliendo il team di sviluppo.

Mi chiedo cosa possa pas­sare nella testa di una persona che fino a quel momento sembrava non riuscire a staccare il piede dall’acceleratore? Soffrirà di una specie di sindrome dell’arto fantasma? Oppure non ha ancora abbandonato del tutto l’idea? Forse non è ancora finita…

Grande racconto, leggetelo!

  1. Sì, tutto è iniziato tredici anni fa:
    As one patient fan pointed out, when development on Duke Nukem Forever started, most computers were still using Windows 95, Pixar had made only one movie — Toy Story — and Xbox did not yet exist.

  2. Wow, how many times have you been here, near the finish line, and you thought you were way out?

  3. Unrealized projects

Contro i frameworks?

Thursday, December 24th, 2009

Se dò una scorsa ai libri sugli scaffali ne trovo diversi che parlano dei frameworks e delle loro virtù, a partire dal venerabile Design patterns. Cito:

The framework dictates the architecture of your application. It will define the overall structure, its partitioning into clas­ses and objects, the key responsibilities thereof, how the clas­ses and object collaborate, and the thread of control. A framework pre­defines these design para­meters so that you, […], can concentrate on the specifics of your application.

[…]

Reuse on this level leads to an inversion of control between the application and the software on which it’s based.

D’altra parte, come forse ricordano quelli che si sono dati la pena di leggere i miei pensieri sul mapping OR, sanno che non li ho più in gran simpatia.

Frameworks vs librerie

Il motivo fondamentale è che c’è una tensione irriducibile fra il riutilizzo (di codice, di impostazioni, di patterns) promosso da un framework e la relazione inversa fra estensione del codice e riutilizzabilità.

Più il framework è esteso meno è efficiente, dove, in questo contesto, l’efficienza è data dalla estensione rispetto alla porzione realmente utilizzata.

Inoltre – e soprattutto – un framework nasce con la pre­messa che l’inversione di controllo 1 sia una buona cosa. Ma è poi vero?

Se siamo incerti sul da farsi un framework risulta pre­zioso perché, in un certo senso, non pos­siamo sbagliare, appunto grazie all’inversione di controllo che detterà a grandi linee i punti in cui inserirci. Viceversa, una libreria “normale” funge da semplice deposito di codice che viene chiamato dall’applicazione, nei modi e nei tempi ritenuti opportuni.

Quando e come conviene usare un framework?

A mio parere, un framework risulta vantaggioso con un progetto nuovo, dove è importante riuscire ad arrivare ad un risultato nel minor tempo pos­sibile ma, idealmente, sarebbe da considerare come un’impalcatura che verrà smontata quando non è più necessaria.

Più facile a dirsi che a farsi, certo, ma non è raro che dopo una prima release un’applicazione venga riscritta senza i framework usati in pre­cedenza, spesso per ragioni di prestazioni.

  1. cito ancora:

    Reuse on this level leads to an inversion of control between the application and the software on which it’s based. When you use a toolkit, you write the main body of the application and call the code you want to reuse. When you use a framework, you reuse the main body and write the code it calls.

Sorprese di fine anno

Saturday, December 5th, 2009

“Non si potrebbe fare…?”

Come se non bastasse il lavoro di rifacimento, è arrivata sulla scrivania la richiesta di una versione che:

  1. possa essere usata in assenza di connettività;
  2. non richieda alcuna procedura d’installazione;
  3. sia multipiattaforma;
  4. sia eseguibile direttamente da una chiave USB.

Insomma, non proprio un qualcosa da fare nei ritagli di tempo!

Cosa usare?

All’inizio avevo pensato ad una qualche applicazione in Java, usando Swing o SWT ma non ho praticamente nes­suna familiarità con applicazioni desktop di quel tipo; inoltre non è affatto scontato che Java sia sempre installata.

Sul lato .Net, conosco qualcosa di più ma non ho idea se sia fattibile uno sviluppo multipiattaforma.

“The simplest thing…”

Poi mi è venuto in mente che avrei potuto risparmiare un bel po’ di lavoro usando una versione rivisitata di quella su web, con la parte server che gira direttamente sul computer dell’utente. Fattibilissimo, certo, ma non proprio una cosa che non richiede alcuna installazione

Infine, credo di aver trovato una soluzione in Tcl, tramite i cosiddetti StarKit e StarPack: un colos­sale (ma leggeris­simo, in MB) uovo di colombo.

In poche parole, uno StartKit consente di impacchettare un’intera applicazione in una specie di disco immagine. Questo disco immagine contiene anche tutti i file ausiliari, binari o meno, anche per diverse piattaforme.

Fin qui niente di particolare. In fondo, con l’avvento delle macchine virtuali è diventata prassi comune distribuire direttamente dei dischi immagine. C’è però da superare l’ostacolo dell’installazione, visto che per eseguire uno StarKit serve comunque un interprete Tcl più una serie di librerie.

E qui entrano in scena gli StarPack, che accorpano uno StarKit e l’interprete in unico file, configurato per partire con un doppio click. Il peso? Pochi MB!

Nei pros­simi giorni vedremo un po’ più in dettaglio come funziona questa tecnologia.

Life goes on

Nel frattempo, sul fronte del refactoring, ho terminato l’accorpamento di tutto l’SQL. Adesso dovremo cominciare a tagliare i ponti con le sezioni che si appoggiano a Joomla.

PS · Va pre­cisato che con l’arrivo ormai pros­simo dell’HTML 5, diventerà concepibile pensare a soluzioni che pos­sano girare in completa autonomia, senza alcuna connes­sione. Tuttavia, il supporto da parte di Explorer è ancora incompleto.

Una questione di ritmo

Thursday, November 19th, 2009

Quando si ha a che fare con un’applicazione già in produzione, soprattutto su web, diventa importante trovare il modo più efficace per raggiungere i risultati desiderati.

Ad esempio, nel progetto a cui sto lavorando, dopo aver deciso alcuni obiettivi a medio termine, ho dovuto pianificare la strada per arrivarci. Anzi, ho dovuto rinunciare a partire subito nella dismis­sione di Joomla per concentrarmi su sull’estrazione di tutti gli statement SQL sparpagliati ovunque.

Senza questo lavoro pre­paratorio, che è in corso (insieme a tutto il resto, ovviamente), rimuovere Joomla sarebbe stato semplicemente troppo rischioso.

Oltre a quello, però, c’è anche la neces­sità di riuscire ad avvicinarsi all’obiettivo un passo alla volta. Perché il problema non è solo quello di minimizzare i rischi ma anche di adeguare il ritmo dei cambiamenti alle esigenze del progetto.

A piccoli passi

Il motivo fondamentale è che quando stiamo riscrivendo una porzione di codice siamo, per così dire, vulnerabili: se arriva una segnalazione di un qualche mal­funzionamento, in un modo o nell’altro, dobbiamo mettere da parte quello che stiamo facendo e dare un’occhiata al problema.

Più il refactoring che stavamo facendo è ampio, più è probabile che il sorgente che stiamo modificando possa essere coinvolto nelle altre modifiche la segnalazione ci potrà richiedere. Può capitare, certo, ma è sempre una buona cosa evitarlo, quando pos­sibile: meno stress, meno incertezze.

Per non rimanere a metà del guado

Sto imparando quindi a “frammentare” i refactoring in modo che non portino via più di un paio d’ore al mas­simo. Si tratta di tempi biblici rispetto a quelli descritti nella letteratura XP, lo so, ma bisogna anche tener conto del fatto che in questo progetto si tratta di interventi su un codice estremamente destrutturato.

I risultati cominciano a vedersi. Fra qualche tempo, sarà divertente andare a confrontare il codice dell’inizio con quello che verrà fuori da tutto questa trafila.

The Big Refactoring: la psicologia del programmatore

Thursday, November 12th, 2009

Nel codice che sto riscrivendo ci sono tante di quelle duplicazioni e una così scarsa strutturazione che non posso fare a meno di chiedermi come sia pos­sibile durante la programmazione fare scelte evidentemente deboli – se non completamente sbagliate – e continuare a farle, ancora e ancora.

È solo perché non c’è tempo? Perché “tanto è la stessa cosa”? Per paura di dover ritoccare del codice?

Under pres­sure

Mi viene il dubbio che molte volte non serva tanto sapere chissà quanti patterns o algoritmi ma fare quello che sappiamo essere la cosa giusta. E per questo, a volte, ci vuole un po’ di coraggio e prendere quel minimo di tempo neces­sario. Spesso, la tentazione di pas­sare oltre, di far finta che il problema non esista, di pensarci domani è troppo forte, siamo troppo stanchi o semplicemente non ce ne frega niente ma almeno dovremmo sentirci almeno un po’ in colpa!

O forse è più un problema di lucidità. Sappiamo che qualcosa non va ma non riusciamo a decidere come porvi rimedio. In ogni caso, coinvolge sempre l’aspetto psicologico, non di competenza tecnica.

The Big Refactoring: alla ricerca dei bersagli facili

Wednesday, November 4th, 2009

Non ho ancora deciso cosa mettere al posto del php, anzi, vorrei rimandare la decisione fino a quando non avrò sistemato il resto.

La lista (una lista?) della spesa

Cos’è “il resto”? Ad esempio, vorrei pas­sare da MySQL a PostgreSQL, da MooTools a YUI; vorrei eliminare orrori come l’implementazione della navigazione tramite POST, usando un approccio il più pos­sibile aderente a principi del REST.

Per cominciare…

Come prima cosa dobbiamo eliminare la dipendenza da Joomla. Per quello che posso capire, l’unica cosa veramente usata in Joomla è la parte relativa alla sicurezza: gli account sono definiti lì ed è stato sviluppato un sistema di autorizzazione che, in parte, vi si appoggia.

Per quanto riguarda l’autenticazione, la mia intenzione sarebbe comunque di pas­sare a DACS, in modo da farla finita con il codice di autenticazione all’interno di un’applicazione e che usa un qualche database.

Prima di quello, però, il punto più facilmente attaccabile è quello dell’autorizzazione, che dovrebbe essere reimplementabile in modo che non usi il database. A livello molto schematico, dovrebbe bastare che l’utente autenticato passi il gruppo di appartenenza per vedersi restituiti i suoi privilegi.

The Big Refactoring?

Monday, November 2nd, 2009

Dopo qualche settimana pas­sata a spegnere incendi, fra poco potrò forse cominciare un ridisegno in grande in stile (1, 2). È un’applicazione che mi ha già fatto scrivere un paio di post, si tratta di una massa informe di MySQL+PHP, che usa Joomla 1.0, con una user experience pros­sima allo zero. L’obiettivo dei pros­simi mesi sarà la sua radicale revisione, al termine della quale potrebbero rimanere solo i dati già salvati.

Mi piacerebbe quindi documentare qui le fasi salienti di questo cammino. È una scommessa un po’ azzardata, lo so, perché queste riprogettazioni a volte si perdono nel nulla o vengono scavalcate da altre urgenze, soprattutto in un sito già in produzione o più banalmente non avrò tempo né voglia di tenere il diario in modo puntuale.

Vediamo come va a finire… ;-)

« Voci Precedenti