giovedì 4 maggio 2017

Docker…Buzzword o nuovo strumento nella nostra cassetta degli utensili?

Tutti quelli che come me sono sempre incuriositi alle novità si sono sicuramente imbattuti in un articolo che, a dir poco, osanna le virtù derivanti dall’utilizzo dei container per lo sviluppo e la distribuzione del software. La tecnologia dei container non è nuova. Disponibile in ambiente linux dalla versione 2.6.4 (intorno al 2004) la tecnologia nota con l’acronimo LXC, sta venendo alla ribalta negli ultimi anni grazie soprattutto alla piattaforma Docker.
Docker
Volendo semplificare all’estremo il concetto si può pensare che la containerizzazione avrà un’impatto in ambito ITC pari almeno a quello avuto dall’introduzione delle macchine virtuali.

In origine erano le Virtual Machines…

Nella mia esperienza l’introduzione tra i miei strumenti del VMWare Player ha portato una piccola rivoluzione nel mio modo di lavorare. All’epoca ero impegnato nello sviluppo di un sistema di visione artificiale integrato nel sistema di controllo di un robot. Il sistema di controllo era implementato su una piattaforma Linux Debian, il che mi obbligava ad avere lo stesso sistema operativo sulla macchina di sviluppo e quindi o avere due computer o, più realisticamente, installare un secondo sistema operativo ed avviare il computer a seconda del progetto al quale stavo lavorando. La possibilità di avere una macchina virtuale che mi consentiva di avere contemporanemante sotto mano sia KDevelop e Debian che Visual Studio e Windows Xp era il massimo in termini di comodità ed efficienza.
Un altro vantaggio derivante dall’utilizzo delle macchine virtuali, è stato quello di poter creare delle macchine di sviluppo molto simili, se non identiche, alle macchine in produzione in termini di pacchetti software installati, versioni particolari di driver di periferica, ecc.. Macchine di sviluppo che, in quanto virtualizzate ho la possibilità mettere da parte al termine della fase di sviluppo e tirar fuori al bisogno nel caso di un aggiornamento, di un bug fix, o di una semplice assistenza.
Last but not Least ora sono in grado di lavorare con qualunque sistema operativo a prescindere da quello installato sull’host (negli ultimi anni macOS su un Macbook Pro).
Il problema di fondo legato all’utilizzo delle macchine virtuali è dato dal peso che ognuna di esse ha in termini di risorse hardware cosa molto evidente se poi parliamo di un laptop. Avere a disposizione più macchine virtuale, o, peggio ancora, mandarne in esecuzione più di una allo stesso momento è un impresa ardua se si dispone di ‘soli’ 16GB di RAM ed un disco SSD da 256GB. E se poi volessimo creare un sistema di sviluppo per SharePoint? IMPOSSIBILE!…o No…?

… E poi arrivò Docker™

A rendere ancora più interessante la faccenda c’è la comunità di utenti pronti a condividere le proprie esperienze su piattaforme come Docker Hub.
Ok, di documentazione e di tutorial on line ce ne sono un’infinità ed in continuo aggironamento, quindi mi asterrò dal ripetere ci che è egregiamente illustrato da persone più esperte di me nel settore. Un buon punto di partenza è la documentazione ufficiale (DOCKER 101: GETTING TO KNOW DOCKER).

Un caso pratico

Un esempio per dare un’idea della semplicità e dell’efficacia dello strumento però è il caso di farlo se non altro per invogliare qualcun altro ad approfondire l’argomento. A tal fine utilizzerò proprio un’esperienza che ho fatto poco tempo fà, per risolvere un problema ad un cliente il cui sito web, a seguito di un attacco informatico, era stato accidentalmente cancellato, e l’ultimo backup disponibile era molto differente nei contenuti dalla versione persa. Il CMS utilizzato per creare il sito era Joomla! nella versione 2.5, ed il database era MySql. La procedura standard per predisporre un ambiente di sviluppo (LAMP, MAMP o WAMP a seconda del sistema operativo) sarebbe, in linea di principio:
- predisporre un server Apache o nginx;
- installare la versione corretta di php;
- installare la versione corretta di MySql;
In alternativa possiamo installare la versione di WAMPServer o MAMP.

WAMP MAMP

Qualunque strada si decida di intraprendere, al termine, il nostro pc sarà configurato per far girare un server Apache, avremo un database MySql ed una versione di php compatibile con la versione del cms del nostro sito web. Non ci resta che copiare il backup nella directory htdocs (o qualunque sia la cartella servita da Apache), modificare il file di configurazione in modo che possa puntare all’istanza di MySql ed al database, aprire il browser, navigare all’indirizzo http://localhost:8080 ed il gioco è fatto, abbiamo una copia del sito web che gira sulla macchina di sviluppo sulla quale fare considerazioni e/o modifiche. Fin qui la procedura classica, e per il mio caso, un intervento su un sito web in particolare può andare più che bene. In fondo, non essendo il mio mestiere, quanti siti web dovrò mai far girare sul mio laptop? Ma se così non fosse? Se arrivasse una richiesta d’intervento su un sito implementato con un cms diverso, con una diversa versione di php, o con un database diverso database? E questo sempre rimanendo nell’area dei cms che girano su server Apache, Php e MySql. Cosa dire poi se al posto di Apache fosse richiesto nginx, se al posto di MySql avessimo bisogno di PostgreSQL o SQL Server, ed al posto di php volessimo Ruby on Rails, o .NET o Java, o python o nodejs. E via dicendo verso l’infinito ed oltre…. Ok possiamo installare tutto (o almeno provarci) sulla nostra macchina e tentare di creare un ambiente di sviluppo simile, per quanto possibile, all’ambiente di produzione…. SIMILE ma certamente NON UGUALE. E anche se ci accontentiamo di SIMILE dobbiamo sempre sperare che nessuno dei pacchetti installati vada in conflitto con qualcos’altro…. Ok, proviamo un’altra strada.
Torniamo al caso di partenza, ovvero Joomla! in una particolare versione.
Una possibilità è quella di creare il nostro container a partire da una versione vanilla di una distribuzione di Linux, e, seguendo il manuale per la creazione di un container custom, installare tutti i componenti di cui abbiamo bisogno. Oppure possiamo ricorrere alle risorse messe a disposizione da altri sviluppatori su repositories come il già citato Docker Hub. Facendo una ricerca con la parola chiave Joomla troviamo ben 158 risultati, cercando, invece mysql, di risultati ne abbiamo 6418!

docker-hub-joomla docker-hub-mysql

Per far girare docker sul nostro sistema di sviluppo, è sufficiente raggiungere la pagina Download Docker Community Edition, selezionare il sistema operativo e scaricare la versione adatta al nostro sistema operativo. Ora, Docker è un sistema che gira in ambiente Linux, ma, con un piccolo accorgimento (in pratica l’installazione di una macchina virtuale) è possibile far girare Docker anche su sistemi Windows o macOS (o OsX).
Portata a termine l’installazione di docker torniamo su Docker Hub, o un altro Registry Docker, e scarichiamo in locale le immagini di nostro interesse lanciando da un prompt dei comandi

$ docker pull joomla

per scaricare l’immagine del container ufficiale di Joomla!, e

$ docker pull mysql

per l’immagine di mysql.

A questo punto abbiamo due possibilità per mandare in esecuzione i due container:
Il primo è quello di mandare in esecuzione i due container lanciando da un prompt dei comandi le due istruzioni opportunamente modificate (per il significato dei parametri si rimanda alle pagine di Docker Hub dedicate ai due container):
$ docker run -it --link some-mysql:mysql --rm mysql sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p"$MYSQL_ENV_MYSQL_ROOT_PASSWORD"'

$ docker run --name some-joomla --link some-mysql:mysql -p 8080:80 -d joomla
La seconda possibilità è quella di utilizzare Docker Compose e quindi:
- creare una cartella dedicata al nostro progetto, all’interno della quale creeremo un file YAML denominato docker-compose.yml (il nome deve essere questo), che conterrà le seguenti istruzioni:

########################### 
# Joomla! CMS Container 
joomla: 
# nome univoco del container 
container_name: my_web_site

# Definisce se il container deve essere riavviato 
#   (i pssibili valori sono no, always e on-failure)
restart: always

# Definisce l'immagine da cui avviare il container
image: joomla

# Collega il container ad un altro servizio
# (in questo caso a quello nel quale gira l'immagine di mysql)
links:
- joomladb:mysql

# Espone la porta, interna al container, all'esterno sulla porta 8080
ports:
- 8080:80

# Esegue il mount di una cartella del container (/var/www/html)
# su una dell'host (./html)        
volumes:
- ./html/:/var/www/html        
# Joomla! CMS Container
###########################

###########################
# MySql Container
joomladb:
container_name: my_web_site_db
restart: always
image: mysql
# Aggiunge alle variabili d'ambiente del container
# la password assegnata all'istanza di mysql (top of security XD)
environment:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
volumes:
- ~/usr/local/var/mysql/:/var/lib/mysql
# MySql Container
###########################

- dalla linea di comando lanciare l’istruzione nella directory contenente il file di sopra:

$ docker-compose up -d 

- Apriamo un browser all’indirizzo http://127.0.0.1:8080/ e otterremo

docker-joomla

ed abbiamo il nostro CMS funzionante e pronto ad essere configurato per una nuova installazione. Se invece abbiamo necessità di operare localmente con il backup di un sito esistente, non ci resta che copiare il backup stesso nella cartella html, sovrascrivendo i files e le cartelle copiate dal container Joomla!, caricare sul server mysql l’ultimo backup del database, ed il gioco è fatto.
- quando finiamo di operare, per terminare i due container, lanciamo il comando:
$ docker-compose down

Conclusione

Quello illustrato è solo uno dei tanti esempi che dimostrano la semplicità con cui è possibile approntare una workstation di sviluppo per un numero enorme di tecnologie.
Ad oggi ho già sperimento le configurazioni ottimali per valutare diversi CMS (Wordpress, prestashop, …), con vari database (MySql, mariadb, mongodb, postgres, …), diversi server (apache, nginx, …), e via dicendo senza aver bisogno di installare niente sulla mia macchina di sviluppo e senza dover creare nuove macchine virtuali.
Forse la tecnologia non è ancora pronta per un utilizzo massivo in ambienti di produzione, anche se tutte le maggiori piattaforme cloud si sono attrezzate per supportare la tecnologia, ma le premesse sono ottime.

Enjoy

mercoledì 4 gennaio 2017

Angular 2 & Reactive Programming

Rx Angular 2
Il mio primo approccio con la Programmazione Reattiva (o meglio FRP - Functional Reactive Programming) risale ormai a metà del 2013 quando, dopo aver letto una serie di articoli che descrivevano il paradigma di programmazione, ho deciso di introdurre un'implementazione di tale tecnica per .NET in una nuova release di un sistema MES (Manufacturing Execution System) che ho progettato ed implementato per l’azienda per cui lavoravo all’epoca. La libreria in questione era denominata Rx - Reactive Extensions.

In parole povere, le diverse implementazioni di Rx, consentono di manipolare flussi di dati e/o eventi (Observable) al fine di semplificare notevolmente lo sviluppo di codice asincrono mediante una sottoscrizione, Subscription, alle variazioni di tali flussi.

Tra i vari benefici che si possono avere con l’adozione di tali tecniche, quella che, a mio parere, è quella di maggior pregio, è data dal fatto che una volta creato uno stream di dati, diversi client possono effettuare una sottoscrizione alle variazioni dello stesso ottenendo, a costo zero, un sistema in grado di comunicare in modo broadcast con diversi client.

Le diverse implementazioni del paradigma, comprendono anche una libreria JavaScript denominata RxJS, e viene installata tra i diversi pacchetti a corredo di Angular 2.
Una delle applicazioni che meglio si prestano all’utilizzo della programmazione reattiva in un’applicazione Angular 2 è data dalla fruizione di un servizio REST.
Nei post precedenti abbiamo creato sia un’applicazione client basata su Angular 2 che una Web API sviluppata con ASP.NET Core, ed entrambe hanno trovato posto su Azure.
Riprendendo il post precedente RESTful API con Swagger, e concentrandoci sul dettaglio della lista dei metodi esposti dal servizio, abbiamo:
REST API
Con questi metodi, possiamo pensare alla realizzazione di un’applicazione CRUD finalizzata alla gestione di una piccola biblioteca personale.
I sorgenti dell’intero progetto sono disponibili per la consultazione su github, ed il risultato finale, è una Web Application di azure raggiungibile all’indirizzo http://talking-things.azurewebsites.net/#/library.
Il modulo principale è dato dal componente library che si presenta come segue:
libary-main
ed la cui implementazione è data da:

import { Component, OnInit, ViewChild } from '@angular/core';
import { Subscription } from 'rxjs/Rx';
import { Router } from '@angular/router';
import { environment } from '../../environments/environment';
import { BookStoreService } from '../bookstore.service';
import { Book } from '../models/book';
import { ModalComponent } from '../modal/modal.component';

@Component({
  selector: 'app-library',
  templateUrl: './library.component.html',
  styleUrls: ['./library.component.css'],
  providers: [BookStoreService]
})
export class LibraryComponent implements OnInit {

  title = 'Book Store';

  books: Book[] = [];

  errorMessage: string = '';
  isLoading: boolean = true;

  @ViewChild(ModalComponent) modal: ModalComponent;

  private subscription: Subscription;
  constructor(private _router: Router, private _bookStoreService: BookStoreService) { }

  ngOnInit() {
    this.reloadData();
  }

  reloadData() {
    this._bookStoreService
      .GetAll()
      .subscribe(
      b => this.books = b,
      e => this.errorMessage = e,
      () => this.isLoading = false);
  }

  onNew() {
    console.log("onNew");
    this._router.navigate(['/edit', 'new']);
  }

  onEdit(book: Book) {
    console.log('edit ' + book.id);
    this._router.navigate(['/edit', book.id]);
  }

  onDelete(book) {

    var message: string = 'Delete \'' + book.title + '\'?: ';
    this.modal.Title = 'Warning';
    this.modal.show(message);
    this.subscription = this.modal.observable.subscribe(x => {

      if (x) {
        this._bookStoreService.Delete(book.id).subscribe(
          book => {
            let b = this.books.find(item => item.id === book.id);
            let id = this.books.indexOf(b);
            this.books.splice(id, 1);
            if (!environment.production)
              console.log(JSON.stringify(book));
          },
          error => {
            console.log(error);
          }
        );
      }

      this.subscription.unsubscribe();
    });
  }
}

e dal servizio BookStoreService, cuore del vero interfacciamento con il servizio REST:

import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';
import { Observable } from 'rxjs/Rx';

import { Book } from './models/book';

import { environment } from '../environments/environment';

@Injectable()
export class BookStoreService {
    private baseUrl: string;

    constructor(private _http: Http) {
        this.baseUrl = environment.bookStoreApi.server + environment.bookStoreApi.apiUrl + '/books/';
    }

    GetAll(): Observable<Book[]> {

        if (!environment.production)
            console.log(this.baseUrl);

        let books$ = this._http.get(this.baseUrl, { headers: this.GetHeaders() })
            .map(mapBooks)
            .catch(handleError);

        return books$;
    }

    public GetById = (id: string): Observable<Book> => {
        let books$ = this._http.get(this.baseUrl + id, { headers: this.GetHeaders() })
            .map(response => response.json())
            .catch(handleError);
        return books$;
    }

    public Create = (book: Book): Observable<any> => {
        let book$ = this._http.post(this.baseUrl, book, { headers: this.GetHeaders() })            
            .catch(handleError);

        return book$;
    }

    public Update = (id: string, book: Book): Observable<any> => {
        let book$ = this._http.put(this.baseUrl + id, book, { headers: this.GetHeaders() })
            .catch(handleError);

        return book$;
    }

    public Delete = (id: string): Observable<Book> => {
        let book$ = this._http.delete(this.baseUrl + id)
            .catch(handleError);

        return book$;
    }

    private GetHeaders() {
        let headers = new Headers();

        headers.append('Accept', 'application/json');

        return headers;
    }
}

function mapBooks(response: Response): Book[] {
    return response.json().map(toBook);
}

function toBook(r: any): Book {
    if (!environment.production)
        console.log('toBook: ' + JSON.stringify(r));

    let book = <Book>({
        id: r.id,
        title: r.title,
        authors: r.authors,
        publicationYear: r.publicationYear,
        isAvailable: r.isAvailable
    });

    if (!environment.production)
        console.log('Parsed book: ', book);

    return book;
}

function handleError(error: Response) {
    return Observable.throw(error || 'Server error');
}

Per avere un’idea di come l’utilizzo della FRP abbia reso più agevole la gestione della programmazione asincrona focalizziamo la nostra attenzione sul metodo GetAll() che ritorna uno stream Observable


    GetAll(): Observable<Book[]> {

        if (!environment.production)
            console.log(this.baseUrl);

        let books$ = this._http.get(this.baseUrl, { headers: this.GetHeaders() })
            .map(mapBooks)
            .catch(handleError);

        return books$;
    }

ed al quale il componente library effettua una sottoscrizione mediante il metodo subscribe:
     this._bookStoreService
      .GetAll()
      .subscribe(
      b => this.books = b,
      e => this.errorMessage = e,
      () => this.isLoading = false);

In modo analogo vengono implementate le interfacce verso gli altri metodi esposti dal servizio REST.
Facile e pulito…

Enjoy