Microservizi

Tutti i microservizi che compongono la piattaforma devo rispettare i seguenti standard e regole generali

Tutti i servizi devono rispettare i 12factor, descritti in modo anche piĂš dettagliato nell'articolo An illustrated guide to 12 Factor Apps

Packaging

Obiettivo

Ogni microservizio deve poter essere eseguito facilmente in locale, anche da chi non conosce a fondo il progetto. L’obiettivo del packaging è:

  • facilitare l’avvio dell’applicativo;

  • semplificare lo sviluppo e il debugging;

  • rendere accessibile un ambiente funzionante con un semplice docker-compose up.

Struttura del repository

Nella root del repository devono essere presenti i seguenti file:

File
Descrizione

Dockerfile

Build dell’immagine base del servizio

docker-compose.yml

Definisce i container minimi per l’esecuzione in produzione o ambienti CI

docker-compose.dev.yml

Contiene configurazioni utili per lo sviluppo locale (es. build, mount volumi, strumenti debug)

.dockerignore

(opzionale) Esclude file e cartelle non rilevanti dalla build


Principi e buone pratiche

  • Il file docker-compose.yml deve essere autosufficiente: chi lo scarica deve poter eseguire il servizio senza dover clonare l’intero repository.

  • Le immagini Docker utilizzate devono essere:

    • disponibili su un registry pubblico (es. DockerHub, GitHub Container Registry ecc.);

    • oppure facilmente costruibili in locale tramite override.

⚠️ Non includere la chiave build: nel docker-compose.yml principale, per evitare conflitti nei contesti di esecuzione remota (es. CI/CD).

Sviluppo locale

Per lo sviluppo in locale:

  1. Copia o rinomina il file docker-compose.dev.yml in docker-compose.override.yml

  2. Esegui:

  3. Docker gestirà automaticamente il merge tra docker-compose.yml e docker-compose.override.yml (vedi: Docker Docs – Multiple Compose Files).

✅ In questo modo, chiunque può avviare l’ambiente di sviluppo senza modificare i file originali.

Esempio di uso dei file

docker-compose.yml (semplificato)

docker-compose.dev.yml

Dopo aver rinominato docker-compose.dev.yml in docker-compose.override.yml, il comando:

...utilizzerà in automatico l’immagine build locale con i volumi montati per lo sviluppo.

Terminazione Pulita

Obiettivo

Ogni microservizio deve essere in grado di gestire correttamente la terminazione, in modo da:

  • garantire la consistenza dei dati;

  • evitare la perdita di eventi o operazioni incomplete;

  • chiudere le risorse in uso in maniera sicura (DB, Kafka, file, socket...).

La gestione corretta della terminazione è essenziale sia in ambiente Docker che in Kubernetes, dove il segnale di terminazione (SIGTERM) viene inviato prima di un restart o di un downscaling.

Segnali da gestire

Segnale
Significato
Comportamento atteso

SIGTERM

Richiesta di terminazione gentile

Il servizio avvia lo shutdown controllato

SIGKILL

Terminazione forzata e immediata

Non gestibile dal codice; da evitare

✅ Il servizio DEVE catturare SIGTERM e avviare una sequenza di terminazione controllata.


Comportamento atteso

Alla ricezione di SIGTERM, il servizio deve:

  • smettere di accettare nuove richieste o eventi;

  • completare le operazioni critiche in corso;

  • liberare risorse (connessioni, file, lock...);

  • loggare la terminazione a livello INFO;

  • uscire con exit code 0 entro un tempo ragionevole.

Checklist – Terminazione Pulita

Verifica
Descrizione
Esito

Cattura SIGTERM

Il servizio intercetta correttamente il segnale SIGTERM

☐

Terminazione controllata

Avvia una sequenza di chiusura ordinata

☐

Timeout ragionevole

Si chiude in max 10s (o valore configurato)

☐

Operazioni critiche completate

Nessuna perdita o corruzione dati

☐

Chiusura delle risorse

Socket, consumer, file, connessioni DB...

☐

Log di terminazione

Esempio: "SIGTERM ricevuto, shutdown in corso..."

☐

Nessuna nuova richiesta

I listener vengono disattivati

☐

Exit code 0

Il processo si chiude correttamente

☐

Esempio Python – uvicorn + asyncio

Esempio Go – signal.NotifyContext

Script di test automatico

Usare questo script per validare la terminazione pulita in locale o integrarlo nella CI.

  • Se si usa docker-compose, si può integrare il test nel CI lanciando:

Configurazione

Principi

La configurazione dei microservizi deve avvenire:

  • principalmente tramite variabili d’ambiente;

  • opzionalmente tramite:

    • file .env (per ambienti locali);

    • parametri CLI (per job schedulati o debug interattivo).

Best practice

Metodo
Contesto di utilizzo

Variabili d’ambiente

Default per tutti i deployment (prod/dev/test)

.env file

Sviluppo locale o test manuali

CLI parametri

Script cron, job schedulati, test/debug

❗ Le configurazioni non devono essere hardcoded nel codice. Utilizzare sempre default sicuri e sovrascrivibili.

Healthcheck

Obiettivo

Permettere al sistema di orchestrazione (Docker/Kubernetes) di verificare che il servizio sia vivo e funzionante.

HTTP microservizi

  • Deve esporre un endpoint /status:

    • 200 OK se tutto è funzionante;

    • codice diverso in caso di errore.

Altri microservizi

  • Se il servizio non è HTTP, l’healthcheck può essere:

    • la presenza di un file di stato;

    • la verifica di un processo in esecuzione.

✅ L’healthcheck DEVE essere incluso nel Dockerfile.

Logging

Principi generali

  • Tutti i microservizi devono implementare un sistema di log coerente, strutturato e facilmente aggregabile.

  • I log devono supportare almeno i livelli: DEBUG, INFO, ERROR. Una gestione completa prevede sei livelli (vedi sotto).

  • Ogni log di errore o anomalia deve essere su una singola riga, facilmente parsabile (plaintext key=value o JSON).

  • Evitare log verbosi e non strutturati: ostacolano il monitoraggio e la correlazione cross-microservizio.

⚠️ Non mischiare log di tipo HTTP e stacktrace multilinea.

  • Scrivere i log a singola riga su stdout.

  • Scrivere stacktrace (se necessario) separatamente su stderr.

Formato e contenuto dei log

  • Preferibile l’uso di log in formato JSON puro, ma solo se interamente strutturato.

  • È vietato:

    • Mischiare testo libero e JSON nello stesso log.

    • Includere payload JSON grezzi come stringa in un campo JSON (es. loggare interamente l’evento Kafka).

Campi richiesti in ogni log rilevante

Campo
Obbligatorio
Descrizione

level

✅

Definisce il tipo di log (INFO, ERROR, DEBUG, CRITICAL, WARNING, ecc.)

timestamp

✅

In UTC, formato RFC 3339, con T e Z maiuscoli

environment

✅

Definisce l'ambiente in cui il microservizio è in esecuzione

event_id

⚠️

Identificatore univoco dell’evento (se applicabile)

event_type

✅

Tipo di evento o operazione ricevuta

call_type

⚠️

Tipo chiamata: HTTP, CLI, EVENT

topic

⚠️

Kafka topic da cui è stato letto l’evento (se applicabile)

log_message

✅

Messaggio del log (errore o info)

client_ip

⚠️

IP della chiamata in ingresso. Usare X-Forwarded-For quando presente

tenant

✅

identificativo univoco dell'ente a partire dal quale è stato generato il log

provider, application, service

✅

Contesto operativo (se disponibili)

user_id

❌

Se presente, deve essere un riferimento anonimo (es. ID utente). Mai dati personali in chiaro

All’avvio, ogni servizio DEVE loggare la versione in esecuzione e l’ambiente (es. local, boat-qa, boat-prod).


Livelli di log

Level
Description
Required

CRITICAL

Fallimento grave, con perdita di dati o che comporta lo l'uscita dal flusso di esecuzione (shutdown) del servizio per impossibilità a proseguire o perchÊ è piÚ safe non proseguire.

No

ERROR

Anomalia che non è possibile gestire e che avrà effetti sul risultato. A ERROR, il rate dei log che si alza dovrebbe essere indice che ci sono problemi per i quali è importante attrarre l'attenzione degli amministratori. Attenzione a non confondere errori che si verificano su sistemi esterni e che non sono nostri errori. Date sempre per scontato che una riga di errore prodotta dovrebbe sempre essere letta da qualcuno, altrimenti meglio esporre un livello di warning o non loggare proprio. Una anomalia dovrebbe sempre dare origine a una e una sola riga di log.

Si

WARNING

Anomalia che non avrebbe dobuto presentarsi ma che è stata gestita correttamente per non comportare errori nel nostro servizio. Ad esempio ho ricevuto un evento con una versione negativa, ma l'ho ignorato. Oppure non sono riuscito a collegarmi al db la prima volta, ma solo dopo 2 tentativi.

Si

INFO

Comunica un cambiamento di stato del servizio. Un evento significativo produce una riga di log per ogni evento o chiamata ricevuta dall'applicativo. Se una transazione o un evento gestito presenta errori NON si deve produrre la riga di INFO e la riga di ERROR, ma solo la seconda.

Si

DEBUG

Questo è il livello in cui comunicare informazioni diagnostiche, non necessarie se non si sta investigando un errore specifico. Devono essere informazioni utili a comprendere errori, non semplicemente a comprendere il flusso interno del software, per il quale esiste il livello apposito (TRACE). Una informazione utile è dare tutto il contesto che permette di comprendere perchÊ il servizio si sta comportando in un certo modo.

No

TRACE

Questo livello è inteso per tracciare il flusso interno del servizio, in modo molto dettagliato. Per esempio si può inserire un log a questo livello per capire in quale ramo del codice siamo finiti con l'esecuzione, oppure si può inserire una riga all'inizio e una alla fine di certe porzioni di codice rilevanti.

No

✅ Ogni anomalia deve generare una e una sola riga di log a INFO, i dettagli vanno a DEBUG.


Esempi di log ben formattati

JSON strutturato

Plaintext key=value

Checklist di qualitĂ  per log ERROR

Usare questa checklist in review e PR:

Monitoring errori

Obiettivo

Raccogliere centralmente gli errori per individuare rapidamente problemi in produzione.

Requisiti

  • Ogni microservizio DEVE integrare Sentry.

  • Una volta configurato:

    • abilitare l’integrazione con Sentry;

    • definire regole di alert personalizzate, se quelle standard non bastano.

📬 Usare SENTRY_DSN come variabile d’ambiente per collegare il servizio a Sentry.

Monitoring metriche

Obiettivo

Esportare metriche di stato e performance per il monitoraggio continuo.

Endpoint Prometheus

  • Se HTTP, il microservizio DEVE esporre un endpoint /metrics in formato Prometheus.

Cosa monitorare

Tipo
Obbligatorio
Descrizione

Error counters

✅

Numero errori critici/intermittenti

Response histograms

✅

Tempi di risposta di servizi esterni

Status code metrics

⚠️

Solo se utili (es. 500 frequenti) – altrimenti usiamo Traefik

404 o bot traffic

❌

NON deve essere esposto come metrica – produce rumore inutile

Cron

Principio

Non reinventare un sistema di schedulazione dentro l’applicazione. Usare invece l’orchestratore.

Linee guida

  • NON implementare schedulatori interni (es. cron manuale, loop time-based).

  • Il servizio deve essere idempotente: rieseguibile sugli stessi dati senza effetti collaterali.

Esecuzione corretta

Implementare task cron come comandi CLI, eseguibili con la stessa immagine Docker:

Il job verrĂ  poi schedulato tramite clustered cron (es. crazy-max/cronjob).

Parametri e file di configurazione

Principio

Niente overengineering: Docker impone giĂ  un mapping tra esterno e interno.

Best practice

Elemento
Regola

Path interni

Usa path fissi tipo /data, senza preoccuparsi del mapping esterno

Configs

Usare Docker Swarm Configs per file di configurazione

Secrets

Usare Docker Swarm Secrets per credenziali e dati sensibili

NO volumes

Evitare l’uso di volume mapping per la configurazione

✅ Questo approccio garantisce sicurezza, semplicità e prevedibilità.

Continuous Integration

Obiettivo

Standardizzare la CI tra i progetti, garantendo efficienza, velocitĂ  e qualitĂ .

Requisiti

  • Ogni progetto DEVE includere la CI condivisa:

  • Eventuali step personalizzati (es. test, lint, coverage) vanno inseriti dopo lo include.

  • Devono riutilizzare la build giĂ  fatta tramite artifact, evitando rebuild inutili.

Performance CI

  • Durata massima della CI: 15 minuti

  • Durata consigliata: ≤ 5 minuti

⚡ Ottimizzare usando:

Test publiccode.yml

Per progetti pubblicati su Developers Italia:

Last updated

Was this helpful?