Cronache dal mondo di GO
Il mio framework
La mia sperimentazione del linguaggio GO sta procedendo spedita e, dopo avere fatto un po’ di pratica con applicazioni giocattolo, ho cominciato ad utilizzarlo nel mondo vero, creando un po’ di script stand alone e, soprattutto, creando un piccolo framework MVC, clone di quello che utilizzo in Python.
So che ormai va di moda creare tutto a servizi ed utilizzare siti fatti con tonnellate di Javascript in Angular, React o Vue, ma per il momento mi sono limitato ad utilizzare un approccio più “tradizionale”, creando un sistema basato su:
- Un server GO che utilizza il modulo http nativo e poco altro per gestire il routing.
- Il server risponde con delle route che sono in grado di ritornare JSON quando richiesto, oppure di renderizzare un template HTML quando necessario.
- Il template contiene HTML e Javascript che può a sua volta recuperare dati con chiamate remote al server GO.
Il sistema è basato sulle indicazioni ricavate dagli ottimi libri di Alex Edwards, ma sviluppato secondo le mie preferenze che sono un po’ diverse dalle sue.
Tra i moduli aggiuntivi che ho utilizzato ci sono:
- GORM per l’accesso ai database.
- GO-WKHTMLTOPDF per generare i PDF.
- REDISSTORE per salvare i dati di sessione in un db redis.
- AWSSDKGO per utilizzare i servizi di Amazon S3 e SES.
- AMQP per le code Rabbit
- EXCELIZE per la creazione di file Excel
La mini applicazione che sto sviluppando sostituisce un sistema di invio newsletter che avevo già realizzato in Python ed ho cercato di utilizzare tutte le tecnologie che uso normalmente per verificare quanto fosse difficile utilizzarle in GO: RabbitMQ, Amazon SES e S3, Redis, generazione PDF e Excel.
Motivazioni
Qualcuno si potrebbe chiedere che senso abbia imparare un nuovo linguaggio e spendere tempo nel ricostruire cose che già posso fare in quello che usavo prima, specialmente se il vecchio strumento è Python che da anni regna incontrastato come il linguaggio numero uno al mondo. La risposta a questa domanda è complessa e molto personale, ma diciamo che potrei riassumerla nei seguenti punti:
- Ricerca e sviluppo Ogni tanto è necessario (almeno per me) uscire dalla propria zona di confort, sperimentare tecnologie e strumenti che poi magari abbandoneremo, ma che ci aiutano a crescere e a interiorizzare modelli e concetti che non conoscevamo in precedenza.
- Superamento dei limiti di Python
- Velocità e concorrenza Anche se Python è un ottimo linguaggio ed è molto apprezzato, ha delle limitazioni intrinseche che difficilmente saranno superate nel tempo, specialmente quelle legate al suo essere uno strumento che non sfrutta al meglio la concorrenza ormai intrinseca nei processori moderni. Questo non è molto problematico nelle applicazioni web che io tipicamente sviluppo, perché la velocità dell’handshake SSL, la latenza di rete, la latenza del DB e tanti altri fattori tendono a livellare le prestazioni. Quando però si ha la necessità di gestire una grande mole di dati in un tempo brevissimo questo può diventare un limite importante e in questi frangenti sicuramente la velocità di GO e la sua semplicità nel gestire in modo nativo la concorrenza diventano preziosi. Python sta diventando un po’ più veloce nella versione 3.11, ma sicuramente non potrà mai raggiungere la velocità di GO, essendo un linguaggio interpretato e difficilmente potrà superare limiti alla concorrenza come il suo GIL (Global Interpreter Lock).
- Tipizzazione statica Al crescere delle dimensioni di un progetto diventa sempre più difficile gestire gli errori che inevitabilmente commettiamo sviluppandolo e, nonostante in Python ci si possa aiutare con type hint e linter, la tipizzazione statica di GO è una vera manna dal cielo. Certo ci costringe ad un piccolo sforzo in più durante la codifica, ma ci avverte in modo implacabile di errori in fase di compilazione che altrimenti non avremmo visto se non per i loro disastrosi effetti durante l’uso.
- Gestione degli errori Python adotta un approccio ad eccezioni che è molto comune, ma anche molto incline ad un utilizzo sbagliato. GO invece adotta un approccio che a molti non piace, ma che io invece trovo fantastico e che ti costringe a gestire i problemi per forza, riducendo ancora la quantità di errori logici che possono essere commessi durante la fase di sviluppo.
- Metodologia di sviluppo e filosofia Qui entriamo proprio nel campo della filosofia, ma a me la semplicità è sempre piaciuta. Non ho mai sopportato i linguaggi verbosi, quelli dove per scrivere sullo schermo “Ciao” devi capire prima 100 concetti e soprattutto non ho mai veramente sopportato l’approccio object oriented. GO è il miglior compromesso che ho trovato tra la semplicità e i vantaggi di un linguaggio compilato e tipizzato staicamente. E’ poco più oneroso di Python durante la scrittura, è velocissimo nella compilazione, tanto da sembrare interpretato, ha 25 keyword e ne puoi imparare le basi in una giornata, ma allo stesso tempo è potente e completo. Certo il fatto che sia un linguaggio con Garbage collection non lo rende adatto alla programmazione di sistema, ma non è il settore nel quale io lavoro. Se invece sviluppate applicazioni web, servizi, micro-servizi e roba del genere, dateci un’occhiata perché potreste innamorarvene.
Impressioni reali di utilizzo
Ma, filosofia a parte, com’è utilizzarlo per un progetto concreto? Beh direi che la risposta è che è davvero uno strumento produttivo ed efficace, con il quale si sviluppa comunque rapidamente, sebbene non quanto lo si possa fare in Python, ma con tutti i vantaggi elencati sopra.
Per lo sviluppo ho adottato una semplice macchina virtuale, con lo stesso ambiente Ubuntu che userei in produzione, nella quale lancio il mio server con un semplice go run <nomefile.go>, riavviandolo quando faccio modifiche (anche se ho letto di moduli che fanno un live reload).
Per la pubblicazione in produzione invece ho fatto un semplice script che compila quello che mi serve già con le caratteristiche della macchina di destinazione (AMD64 anziché ARM) e poi copia sul server finale solo i file binari e quelli HTML, css, js e statici necessari.
Qui sotto un piccolo esempio per dare l’idea, dove, oltre al web server go, ci sono anche altri servizi aggiuntivi compilati e poi sostituiti a servizi già installati.
#!/bin/bash
BASEFOLDER=/home/fgiamma/labs3
DESTFOLDER=/home/labs3/labs3
REMOTESERVER=root@phoenix.fgiamma.org
# echo "Compiling..."
env GOOS=linux GOARCH=amd64 go build -o labs3 ./cmd/web/main.go
env GOOS=linux GOARCH=amd64 go build -o s3uploader-go ./cmd/cli/s3/s3uploader/s3uploader.go
env GOOS=linux GOARCH=amd64 go build -o rabbitclient-go ./cmd/cli/rabbit/client/client.go
env GOOS=linux GOARCH=amd64 go build -o rabbittest-go ./cmd/cli/rabbit/server/server.go
env GOOS=linux GOARCH=amd64 go build -o createreminderpdf-go ./cmd/cli/pdf/createreminderpdf.go
# echo "Stopping services..."
ssh $REMOTESERVER 'systemctl stop labs3'
ssh $REMOTESERVER 'systemctl stop s3uploader'
ssh $REMOTESERVER 'systemctl stop sendmailgo'
ssh $REMOTESERVER 'systemctl stop createreminderpdf'
# echo "Rsyncing files..."
/usr/bin/rsync -a --no-relative --delete ./labs3 $REMOTESERVER:$DESTFOLDER
/usr/bin/rsync -a --no-relative --delete ./s3uploader-go $REMOTESERVER:$DESTFOLDER
/usr/bin/rsync -a --no-relative --delete ./rabbitclient-go $REMOTESERVER:$DESTFOLDER
/usr/bin/rsync -a --no-relative --delete ./rabbittest-go $REMOTESERVER:$DESTFOLDER
/usr/bin/rsync -a --no-relative --delete ./createreminderpdf-go $REMOTESERVER:$DESTFOLDER
echo "Rsyncing folders..."
/usr/bin/rsync -av --no-relative --include="*/" --include="*.html" --exclude="*" --delete $BASEFOLDER/cmd/web/pages $REMOTESERVER:$DESTFOLDER
/usr/bin/rsync -av --no-relative --delete $BASEFOLDER/ui --exclude="uploads" $REMOTESERVER:$DESTFOLDER
echo "Create remote uploads folder"
/usr/bin/ssh $REMOTESERVER "mkdir -p $DESTFOLDER/ui/static/uploads"
# echo "Restarting services..."
ssh $REMOTESERVER 'systemctl start labs3'
ssh $REMOTESERVER 'systemctl start s3uploader'
ssh $REMOTESERVER 'systemctl start sendmailgo'
ssh $REMOTESERVER 'systemctl start createreminderpdf'
echo "Install completed"
Insomma riassumendo:
GO è un ottimo strumento, veloce e produttivo, che consente di sviluppare applicazioni solide e dalla risposta immediata, rallentando in modo marginale lo sviluppo per la sua struttura tipizzata e per il fatto di essere compilato, ma facendo risparmiare una grande quantità di tempo nel trovare e risolvere errori che sarebbero altrimenti invisibili in un linguaggio interpretato.
Vogliamo citare tra i vantaggi anche il fatto di non mandare più in giro il codice sorgente? 😉