> For the complete documentation index, see [llms.txt](https://docs.opencityitalia.it/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opencityitalia.it/sviluppatori-e-partner-tecnologici/standard-e-convenzioni/microservizi.md).

# Microservizi

Tutti i servizi devono rispettare i [12factor](https://12factor.net/), descritti in modo anche più dettagliato nell'articolo [An illustrated guide to 12 Factor Apps](https://www.redhat.com/architect/12-factor-app)

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

   ```bash
   docker-compose up
   ```
3. Docker gestirà automaticamente il **merge tra `docker-compose.yml` e `docker-compose.override.yml`** (vedi: [Docker Docs – Multiple Compose Files](https://docs.docker.com/compose/extends/)).

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

### Esempio di uso dei file

**`docker-compose.yml` (semplificato)**

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

**`docker-compose.dev.yml`**

```yaml
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:

```bash
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`

```python
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`

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

```bash
#!/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:

  ```bash
  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.
* Riferimento normativo: [**Allegato 4 delle Linee Guida di Interoperabilità AgID**](https://www.agid.gov.it/sites/agid/files/2024-07/Linee_guida_interoperabilit%C3%A0PA_All4_Raccomandazioni-di_implementazione.pdf).
* 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](https://datatracker.ietf.org/doc/html/rfc3339), 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

<table><thead><tr><th>Level</th><th width="413">Description</th><th>Required</th></tr></thead><tbody><tr><td>CRITICAL</td><td>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ù <em>safe</em> non proseguire.</td><td>No</td></tr><tr><td>ERROR</td><td>Anomalia che non è possibile gestire e che avrà effetti sul risultato. A ERROR, il <em>rate</em> 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. <strong>Una anomalia dovrebbe sempre dare origine a una e una sola riga di log.</strong></td><td>Si</td></tr><tr><td>WARNING</td><td>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.</td><td>Si</td></tr><tr><td>INFO</td><td>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.</td><td>Si</td></tr><tr><td>DEBUG</td><td>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.</td><td>No</td></tr><tr><td>TRACE</td><td>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.</td><td>No</td></tr></tbody></table>

> ✅ **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**

```json
{
  "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:

* [ ] Il log è su una singola riga?
* [ ] Include `event_id`, `event_type`, `topic`, `microservice`, `error`?
* [ ] I dati personali sono esclusi?
* [ ] Il messaggio è leggibile e utile senza stacktrace?
* [ ] È facilmente aggregabile da dashboard/log viewer?

## Monitoring errori

### Obiettivo

Raccogliere centralmente gli errori per individuare rapidamente problemi in produzione.

### Requisiti

* Ogni microservizio **DEVE integrare** [**Sentry**](https://sentry.io/).
* 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 <a href="#user-content-cron" id="user-content-cron"></a>

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

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

Il job verrà poi schedulato tramite **clustered cron** (es. [`crazy-max/cronjob`](https://github.com/crazy-max/diun/blob/master/.github/workflows/cron.yml)).

## 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**](https://docs.docker.com/engine/swarm/configs/) per file di configurazione       |
| **Secrets**      | Usare [**Docker Swarm Secrets** ](https://docs.docker.com/engine/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**:

```yaml
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`.
* Devono **riutilizzare la build già fatta** tramite [artifact](https://docs.gitlab.com/ee/ci/jobs/job_artifacts.html), evitando rebuild inutili.

### Performance CI

* Durata **massima** della CI: **15 minuti**
* Durata **consigliata**: **≤ 5 minuti**

> ⚡ Ottimizzare usando:
>
> * build [multi-stage](https://docs.docker.com/build/building/multi-stage/) Docker;
> * [cache di GitLab](https://docs.gitlab.com/ee/ci/caching/);
> * test selettivi.

### Test `publiccode.yml`

Per progetti pubblicati su [Developers Italia](https://developers.italia.it/):

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.opencityitalia.it/sviluppatori-e-partner-tecnologici/standard-e-convenzioni/microservizi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
