🛠️
Opencity Italia
Sviluppatori e partner tecnologici
Sviluppatori e partner tecnologici
  • Introduzione
  • Architettura
    • Pattern: microservizi
    • Pattern: event sourcing
    • Vista generale
  • Standard e convenzioni
    • Standard della piattaforma
    • Microservizi
  • Roadmap
  • Integrazioni
    • Integrazione Widget servizio (FormIO)
    • Integrazioni con il flusso delle pratiche
      • API ReST
      • Webhooks
    • Modello di integrazione con l'area personale
    • Integrazione con Intermediari di pagamento PagoPA
      • Requisiti per l'integrazione
      • Il Pagamento
        • Versione 1.0
        • Versione 2.0
      • Schema di Funzionamento
      • Configurazione dei pagamenti
        • API v1
        • API v2
      • Un pagamento in dettaglio
      • Gli stati di un pagamento
      • Processo di sviluppo
      • Implementazione di un proxy
    • Integrazione con Protocollo Informatico
      • Requisiti per l'integrazione
      • Documento digitale
        • Esempio documento con allegati non protocollato
        • Esempio documento con allegati protocollato
        • Esempio documento con campo retry_meta prodotto dal protocol proxy
        • Esempio documento con campo retry_meta modificato dal sistema di retry
        • Usecase: Il Documento originato dalle pratiche dai servizi digitali
        • Stati del documento
      • Architettura del sistema di protocollazione
      • WorkFlow sistema di protocollazione
        • Configurazione tenant e servizi
      • Protocol Proxy: Specifiche Implementative
      • Processo di sviluppo
    • Integrazioni con il Sito Istituzionale
    • Single Sign-On
      • SSO mediante oAuth2
      • SSO mediante JWT
    • Processi asincroni (job)
      • Importazione dovuti
    • Integrazione di un servizio di terze parti protetto da autenticazione
      • Esempio con GovWay
  • 👩‍💻Sviluppo
    • Multilingua
    • Temi grafici
Powered by GitBook
LogoLogo

Opencity Labs

  • Sito web
  • Product page

Developers Italia

  • Sito web
  • Area personale e Servizi Digitali

Documentazione Opencity Italia

On this page
  • Packaging
  • Obiettivo
  • Struttura del repository
  • Principi e buone pratiche
  • Sviluppo locale
  • Esempio di uso dei file
  • Terminazione Pulita
  • Obiettivo
  • Segnali da gestire
  • Comportamento atteso
  • Checklist – Terminazione Pulita
  • Esempio Python – uvicorn + asyncio
  • Esempio Go – signal.NotifyContext
  • Script di test automatico
  • Configurazione
  • Principi
  • Best practice
  • Healthcheck
  • Obiettivo
  • HTTP microservizi
  • Altri microservizi
  • Logging
  • Principi generali
  • Formato e contenuto dei log
  • Checklist di qualità per log ERROR
  • Monitoring errori
  • Obiettivo
  • Requisiti
  • Monitoring metriche
  • Obiettivo
  • Endpoint Prometheus
  • Cosa monitorare
  • Cron
  • Principio
  • Linee guida
  • Esecuzione corretta
  • Parametri e file di configurazione
  • Principio
  • Best practice
  • Continuous Integration
  • Obiettivo
  • Requisiti
  • Performance CI
  • Test publiccode.yml

Was this helpful?

Export as PDF
  1. Standard e convenzioni

Microservizi

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

PreviousStandard della piattaformaNextRoadmap

Last updated 16 days ago

Was this helpful?

Tutti i servizi devono rispettare i , descritti in modo anche più dettagliato nell'articolo

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:

    docker-compose up

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

Esempio di uso dei file

docker-compose.yml (semplificato)

version: "3.9"
services:
  myservice:
    image: registry.gitlab.com/opencity-labs/myservice:1.2.3
    ports:
      - "8080:8080"

docker-compose.dev.yml

version: "3.9"
services:
  myservice:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./:/app
    environment:
      - ENVIRONMENT=local

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

docker-compose up

...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

import asyncio
import signal
import uvicorn
from myservice import app

APP_HOST = "0.0.0.0"
APP_PORT = 8080

def run_server():
    """Esegue il server intercettando SIGINT e SIGTERM per uno shutdown pulito"""
    config = uvicorn.Config(
        app,
        host=APP_HOST,
        port=int(APP_PORT),
        proxy_headers=True,
        forwarded_allow_ips='*',
        access_log=False,
    )
    server = uvicorn.Server(config)

    loop = asyncio.get_event_loop()

    for sig in (signal.SIGINT, signal.SIGTERM):
        loop.add_signal_handler(sig, lambda: asyncio.create_task(server.shutdown()))

    try:
        loop.run_until_complete(server.serve())
    except asyncio.CancelledError:
        pass  # Evita traceback in console

if __name__ == '__main__':
    run_server()

Esempio Go – signal.NotifyContext

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("pong"))
    })

    srv := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    go func() {
        log.Println("Server in avvio su :8080")
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Errore durante l'avvio del server: %v", err)
        }
    }()

    <-ctx.Done()
    log.Println("SIGTERM ricevuto, avvio terminazione...")

    shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    if err := srv.Shutdown(shutdownCtx); err != nil {
        log.Fatalf("Errore durante la terminazione: %v", err)
    }

    log.Println("Terminazione completata.")
}

Script di test automatico

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

#!/bin/bash
set -euo pipefail

SERVICE_NAME="test_shutdown_service"
IMAGE_NAME="your-image-name"
LOG_FILE="shutdown_test.log"

echo "▶️ Avvio container..."
docker run --rm --name "$SERVICE_NAME" -d "$IMAGE_NAME" > /dev/null

sleep 2  # attesa minima per stabilità

echo "🛑 Invio SIGTERM..."
docker kill --signal=SIGTERM "$SERVICE_NAME"

sleep 2

echo "🔍 Verifica terminazione..."
if docker ps -a --format '{{.Names}}' | grep -q "$SERVICE_NAME"; then
  echo "❌ Il container è ancora in esecuzione"
  exit 1
else
  echo "✅ Terminazione completata correttamente"
fi
  • Se si usa docker-compose, si può integrare il test nel CI lanciando:

    docker-compose -f docker-compose.yml -f docker-compose.override.yml up -d
    docker-compose kill -s SIGTERM

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

✅

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

{
  "level": "ERROR",
  "timestamp": "2025-04-18T10:27:52Z",
  "environment": "boat-prod",
  "event_id": "abcd-1234",
  "event_type": "PaymentRequestReceived",
  "topic": "pagopa-incoming-events",
  "version": "1.3.2",
  "environment": "boat-prod",
  "tenant": "9bee12a4-...",
  "application": "50b4f598-...",
  "service": "0a28427f-...",
  "provider": "sicraweb-wsprotocollodm",
  "client_ip": "10.12.3.1",
  "user_id": "user_92348",
  "status": "error",
  "message": "Impossibile trovare dati di logon validi per l'utente"
}

Plaintext key=value

ERROR 2025-04-18T10:27:52Z event_id=abcd-1234 tracker=252603771125040 provider=sicraweb-wsprotocollodm tenant=9bee12a4 application=50b4f598 service=0a28427f topic=pagopa-incoming-events microservice=payment-handler version=1.3.2 environment=boat-prod client_ip=10.12.3.1 user_id=user_92348 error="Impossibile trovare dati di logon validi per l'utente; nested exception: Connection aborted (Connection reset by peer)"

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

  • 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:

docker run your-image-name python cron_jobs/protocol_batch.py

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

Secrets

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:

stages:
 - build
 - test
 - deploy
 - review
 - dast
 - staging
 - canary
 - production
 - incremental rollout 10%
 - incremental rollout 25%
 - incremental rollout 50%
 - incremental rollout 100%
 - performance
 - cleanup

include:
  - project: 'opencity-labs/product'
    ref: main
    file: '.gitlab/ci/devops.yml'
  • Eventuali step personalizzati (es. test, lint, coverage) vanno inseriti dopo lo include.

Performance CI

  • Durata massima della CI: 15 minuti

  • Durata consigliata: ≤ 5 minuti

⚡ Ottimizzare usando:

  • test selettivi.

Test publiccode.yml

publiccode:
  stage: test
  allow_failure: false
  image:
    name: italia/publiccode-parser-go
    entrypoint: [""]
  script:
    - publiccode-parser /dev/stdin < publiccode.yml
  only:
    - master

Docker gestirà automaticamente il merge tra docker-compose.yml e docker-compose.override.yml (vedi: ).

Riferimento normativo: .

In UTC, formato , con T e Z maiuscoli

Ogni microservizio DEVE integrare .

Il job verrà poi schedulato tramite clustered cron (es. ).

Usare per file di configurazione

Usare per credenziali e dati sensibili

Devono riutilizzare la build già fatta tramite , evitando rebuild inutili.

build Docker;

;

Per progetti pubblicati su :

12factor
An illustrated guide to 12 Factor Apps
Docker Docs – Multiple Compose Files
Allegato 4 delle Linee Guida di Interoperabilità AgID
Sentry
crazy-max/cronjob
artifact
multi-stage
cache di GitLab
Developers Italia
RFC 3339
Docker Swarm Configs
Docker Swarm Secrets