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
Quello che ci si aspetta è che si possa usare il docker-compose.yml scaricandolo direttamente sul proprio pc, possibilmente senza necessità di clonare tutto il repository.
Dockerfile
e docker-compose.yml
devono essere presenti nella root del repo (.dockerignore
se serve) per facilitare l'avvio in locale dell'applicativo e farne saggiare l'utilizzo; Perché sia pienamente usabile è necessario che le immagini facciano riferimento al registry pubblico (nostro o meno).
usare un docker-compose.dev.yml
per aggiungere tool utili per lavorare in locale durante lo sviluppo.
Per esempio la chiave "build" per le immagini create deve essere presente nel file ".dev" e non nel file compose principale.
In questo modo scaricando il repository e rinominando il file .dev in docker-compose.override.yml
ci si mette nelle condizioni ideali per sviluppare il servizio e un docker-compose up
comporterà automaticamente l'uso in cascata dei file docker-compose.yml
e docker-compose.override.yml.
Per ulteriori dettagli consultare il manuale di Docker: Merge compose files.
il servizio deve gestire correttamente i segnali inviati da docker SIGTERM e SIGKILL per fare uno shutdown pulito e senza interrompere l'esecuzione in fasi critiche per la consistenza dei dati.
la configurazione *deve avvenire mediante variabili d'ambiente
la configurazione può essere fatta anche tramite un file di environment (.env
) o parametri da cli
se http, deve essere presente un /status
entrypoint che risponde 200 se va tutto bene, uno status code diverso in caso contrario
se non http, l'healthcheck può essere un controllo su un file o su un processo
l'health check, se disponibile, deve essere inserito nel Dockerfile
Vanno tenute sempre in considerazione le raccomandazioni implementative di Agid, espresse nell'Allegato 4 alle Linee Guida di Interoperabilità.
Il log deve supportare almeno tre livelli: debug
, info
e error
.
debug:
deve essere comprensibile il flusso logico dell'applicativo
info:
ci si aspetta di leggere 1 riga per ogni evento o chiamata ricevuta dall'applicativo, che non ha presentato errori. 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.
error:
non si deve vedere nulla se non ci sono errori. 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.
Non mi schiare log http e log multiriga (stacktrace o similari): se si è costretti, per esempio inviare lo stacktrace a stderr
e riservare stdout
ai log a singola riga
Se possibile implementare log in formato json, ma solo se si può produrre solo del JSON, mischiare testo semplice e JSON non è di grande utilità.
Sul contenuto:
DEVE esserci l'istante della comunicazione in formato UTC (RFC 3339) e con separatori Z e T in maiuscolo.
DEVE contenere la URI o l'identificazione dell'operazione che si sta eseguendo
Dovrebbe contenere la tipologia della chiamata, ad esempio il verbo HTTP o se si tratta di una chiamata fatta da CLI o in un azione batch.
DEVE contenere l'esito della chiamata
Se è una chiamata fatta da un client DEVE essere inserito l'IP sorgente della chiamata (attenzione a rispettare eventuali header X-FORWARDED-XXX)
Se è una chiamata fatta per conto di un essere umano è possibile inserire un riferimento all'utente, ma NON DEVE contenere dati personali in chiaro (il codice fiscale ad esempio deve essere troncato al mssimo alle prime 7 lettere). Per avere un riferimento certo inserire lo userid
della piattaforma.
Se disponibile DEVE inserire l'identificatore unico della chiamata o dell'evento (event_id
).
DEVE contenere l'environment in cui sta girando il microservizio: boat-qa, boat-prod, local
, etc...
Il microservizio DEVE integrare Sentry per il monitoraggio centralizzato degli errori.
Una volta operativa l'integrazione è necessario:
abilitare l'integrazione con sentry sul repo gitlab
inserire regole di allerta per quel microservizio, se le regole esistenti non sono sufficienti
se http, deve esistere l'entrypoint /metrics
che espone metriche in formato prometheus.
un servizio può mostrare metriche sulle chiamate (status code, timing), anche se solitamente le possiamo prendere altrove (traefik), - un servizio deve mostrare metriche specifiche dell'app, in particolare delle condizioni di errore (counters) e dei tempi di risposta di eventuali servizi esterni richiamati da questo (histograms).
il servizio NON deve mostrare metriche che derivano ad esempio da errori 404, perché i bot che scansionano tutte le URL provocano infinite varianti che comportano la creazione di infinite metriche di errore
sui sistemi di deploy e orchestrazione che usiamo ci sono sempre dei modi di eseguire task stile cron, inutile reimplementare quella logica dentro la propria App.
è indispensabile invece rendere il proprio software idempotente: deve essere rieseguibile più volte sugli stessi dati senza fare danno.
il modo più semplice per implementare una chiamata a tempo è implementare un comando da cli che possa essere eseguito con la stessa immagine con cui gira l'applicativo.Questo applicativo viene poi schedulato nel nostro ambiente di produzione istruendo il nostro clustered cron. Per istruzioni sulla configurazione si rimanda alla documentazione di crazymax-cronjob.
in docker c'e' sempre un mapping tra esterno e interno, inutile parametrizzare questi aspetti in modo particolarmente flessibile: il controllo sul filesystem interno è sempre in mano allo sviluppatore del microservizio, quindi si può sempre usare un path interno del tipo /data
senza preoccuparsi tanto del fatto che all'esterno quel path diventerà qualcosa come /srv/data/qualcosa/altro/data.
Per le configurazioni e' importante usare sempre lo strumento configs
di docker swarm, senza ricorrere al volume mapping.
Analogamente per le credenziali o le parti segrete DEVE essere usato lo strumento dei secrets.
Ogni progetto può inserire nella CI attività di linting del codice e enforcemente dei coding styles
Ogni progetto DEVE includere la CI condivisa, nel seguente modo:
Eventuali step di test devono essere inclusi dopo questo snippet di codice e devono sfruttare la fase di build già eseguite, per esempio se viene fatta la build di un binario la si può sfruttare negli step successivi mediante un artifact.
La durata della CI non DEVE mai superare i 15 minuti e non dovrebbe mai superare i 5: questo perché soprattutto in condizioni di emergenza, fare un hotfix non può richiedere troppo tempo, è necessario che ogni step sia ben ottimizzato per ottenere questo scopo, per esempio sfruttando le build multi-stage di docker o la cache di Gitlab CI.
in caso di pubblicazione su Developers Italia, la correttezza del public-code.yml
deve essere testato con:
Riguardo a coding, contribuzioni, eventi, API
Nella piattaforma si fa ampio uso del Pattern Event Sourcing è quindi fondamentale che il formato degli eventi sia stabile e documentato.
Per ogni evento i producers DEVONO inserire sempre i seguenti dati:
id
identificativo unico dell'entità oggetto dell'evento
UUID
Es: in un pagamento è l'ID del pagamento, in una pratica è l'application_id etc...
event_id
identificativo unico dell'evento
UUID
Non deve esserci per nessun motivo due volte lo stesso ID
event_version
versione dell'evento
Integer or Float
E' un intero (Es: 1, 2, etc..) cambia ad ogni cambiamento NON retrocompatibile.
event_created_at
data dell'evento
ISO8601
Ricordarsi di usare il formato della timezone Europe/Rome (Es: 2022-06-22T15:11:20+02:00
)
app_id
Nome e versione dell'applicativo che ha generato l'evento
$application:$version
Utile per motivi di debug (Es: payment-dispatcher:1.0.15).
Attenzione: se si genera un evento, a partire da un evento esistente, si sta creando una nuova entità che avrà i propri valori event_id
, event_created_at
, event_version
, app_id
: non si deve mai ricopiare questi valori dall'evento originario.
I consumatori devono essere sempre attenti alla versione di eventi che supportano: la versione deve essere esplicitamente supportata ed eventi di una versione non supportata devono essere scartati
Nei log è possibile dare evidenza del fatto che si è incontrato un evento non supportato, ma non va loggato come errore è sufficiente dare l'informazione a livello DEBUG. In condizioni normali un servizio che consuma eventi emette una riga di log a livello INFO se e solo se ha gestito l'evento e ha fatto qualcosa con quell'evento che ha avuto successo.
Le date vanno sempre espresse nello standard ISO8601, in particolare, le date non devono contenere i millisecondi, e devono contenere il formato della timezone Europe/Rome. Le letter T e Z DEVONO essere espresse in maiuscolo.
Es: 2022-06-22T15:11:20+02:00
Facciamo in generale riferimento alle linee guida di Zalando, tenendo ovviamente in considerazione anche i requisiti e le raccomandazioni delle Linee Guida di Interoperabilità della PA.
i servizi DEVONO agire solo su verbi HTTP supportati, rispettarne la semantica, restituire errore sugli altri.
i metodi DELETE, GET, HEAD, OPTIONS, PUT, TRACE devono essere idempotenti (vd anche RFC7231)
le API hanno path al plurale sulle collezioni, per separare le parole si usa il kebab-case (il trattino).
nelle risposte le URL dovrebbero essere sempre espresse in modo assoluto
Le collezioni devono essere facilmente navigabili, per questo abbiamo adottato il seguente standard:
Le GET sulle risorse devono USARE laddove supportati i query parameters convenzionali:
sort
offset
limit
cursor
embed
q
fields
Su alcuni campi è utile implementare anche l'opzione di paginazione cursor-based:
GET /payments?created_since=$timeStamp
Fare riferimento alla RFC 7807