Contract First o Muori Provandoci: L’Unico Modo Sano di Progettare API
Devo sfogarmi. Qualcosa che mi rode l’anima dalla prima volta che mi sono unito a un progetto a metà sviluppo e ho posto la fatidica domanda: “Dov’è la documentazione dell’API?”
La risposta, invariabilmente, era una delle seguenti:
- “Controlla la collection di Postman.” (Traduzione: un cimitero di 200 richieste, metà delle quali obsolete, chiamate cose come
GET users FINAL v2 (copy)) - “Guarda semplicemente il codice.” (Traduzione: reverse-engineer il nostro spaghetti e buona fortuna)
- “La documenteremo più tardi.” (Traduzione: non la documenteremo mai)

Questo, amici miei, è la conseguenza del design de API Code First. E sono qui per dirvi che è una malattia. Una malattia contagiosa, che uccide i progetti e si diffonde tra i team come un virus in un ufficio mal ventilato.
La Scena del Crimine: Code First
Lasciate che vi dipinga un quadro. Siete uno sviluppatore backend. Ricevete un ticket: “Crea endpoint per recuperare le transazioni dell’utente.” Aprite il vostro IDE, create un Controller, collegate un Service, restituite qualche DTO che avete inventato sul momento, e fate il push su develop. Fatto. Spedite.
Nel frattempo, lo sviluppatore frontend sta aspettando. Vi chiede: “Com’è strutturata la risposta?” Voi dite: “Chiamalo e vedi.” Lui lo chiama. Vede un campo chiamato txn_dt. Chiede: “È un timestamp? Una stringa? Che fuso orario?” Voi scrollate le spalle. “È quello che Java serializza di default con LocalDateTime.”
Congratulazioni. Avete appena creato un disastro di comunicazione avvolto in un panino di debito tecnico.
Lo sviluppatore mobile, che si è unito alla chiamata cinque minuti dopo, ora deve implementare lo stesso endpoint. Guarda la “documentazione” (la collection di Postman, ricordate?) e trova una richiesta di tre sprint fa che restituisce una struttura completamente diversa perché qualcuno ha rifattorizzato il DTO e si è dimenticato di aggiornare qualsiasi cosa.
Questo è Code First. Scrivete il codice, e il contratto è qualunque cosa il codice produca per caso. La specifica dell’API è un ripensamento, un effetto collaterale, un incidente.
La Salvezza: Contract First
Ora immaginate un universo parallelo dove regna la sanità mentale.
Prima che qualcuno scriva una singola riga di Java, Go, o qualunque veleno preferiate, il team si siede e definisce il contratto. Questo contratto è un file .yaml o .json scritto in OpenAPI (precedentemente Swagger). Specifica:
- Ogni endpoint.
- Ogni metodo HTTP.
- Ogni parametro di richiesta e il suo tipo.
- Ogni corpo di risposta e la sua struttura.
- Ogni possibile codice di errore e cosa significa.
Questo file diventa l’unica fonte di verità. Il backend genera stub del server da esso. Il frontend genera SDK client da esso. Il team mobile genera i propri modelli da esso. Tutti lavorano contro la stessa specifica, sincronizzati come un’orchestra ben diretta.
openapi: 3.0.3
info:
title: Transaction API
version: 1.0.0
paths:
/users/{userId}/transactions:
get:
summary: Fetch user transactions
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: A list of transactions
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Transaction'
'404':
description: User not found
components:
schemas:
Transaction:
type: object
properties:
id:
type: string
format: uuid
amount:
type: integer
description: Amount in cents
currency:
type: string
example: "EUR"
timestamp:
type: string
format: date-time
required:
- id
- amount
- currency
- timestamp
Guardate quello. Bellissimo. Esplicito. Nessuna ambiguità. Il timestamp è un date-time. L’amount è in centesimi (nessun incubo a virgola mobile). Lo userId è un UUID, non “qualunque stringa vi venga in mente di inviare.”
Se il backend devia da questo contratto, il codice generato non compilerà. Se il frontend si aspetta un campo diverso, il client generato urlerà contro di loro. Il contratto impone disciplina.
“Ma Scrivere YAML è Noioso”
Sì. Lo è. Sapete cos’altro è noioso? Passare quattro ore in una videochiamata a debugare perché l’app Android crasha perché qualcuno sul backend ha deciso di rinominare user_id in userId senza dirlo a nessuno.
Sapete cos’è noioso? Riscrivere test di integrazione perché la struttura della risposta è cambiata e nessuno ha aggiornato i mock.
Sapete cos’è crudelmente noioso? Essere il povero disgraziato che si unisce a un progetto di due anni e deve capire 150 endpoint leggendo file Controller sparsi in 47 package senza alcuna documentazione.
Scrivere il contratto in anticipo è un investimento. È come lavarsi i denti: noioso sul momento, ma previene una devitalizzazione dopo.
Il Tooling: Edizione Java
Dato che ho promesso esempi di codice, lasciate che vi mostri come funziona nell’ecosistema Spring. Usiamo un plugin Maven chiamato openapi-generator-maven-plugin. Lo puntate al vostro contratto, e genera le interfacce per voi.
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.2.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/api/openapi.yaml</inputSpec>
<generatorName>spring</generatorName>
<apiPackage>me.doismiu.api.generated</apiPackage>
<modelPackage>me.doismiu.api.generated.model</modelPackage>
<configOptions>
<interfaceOnly>true</interfaceOnly>
<useSpringBoot3>true</useSpringBoot3>
<useTags>true</useTags>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
Eseguite mvn compile, e improvvisamente avete un’interfaccia Java che assomiglia a questa:
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
@Tag(name = "Transactions")
public interface TransactionsApi {
@Operation(summary = "Fetch user transactions")
@GetMapping(value = "/users/{userId}/transactions", produces = "application/json")
ResponseEntity<List<Transaction>> getUserTransactions(
@PathVariable("userId") UUID userId
);
}
Il vostro Controller implementa semplicemente questa interfaccia:
@RestController
public class TransactionsController implements TransactionsApi {
private final TransactionService service;
public TransactionsController(TransactionService service) {
this.service = service;
}
@Override
public ResponseEntity<List<Transaction>> getUserTransactions(UUID userId) {
return ResponseEntity.ok(service.findByUser(userId));
}
}
Se provate a cambiare il tipo di ritorno in qualcosa che non corrisponde al contratto, il compilatore vi schiaffeggerà. Se provate ad aggiungere un parametro che non è nella specifica, il compilatore vi schiaffeggerà più forte. Il contratto è legge, e il codice deve obbedire.
Il Problema Culturale
Ecco la scomoda verità: Contract First non è una sfida tecnica. È una sfida culturale.
Gli sviluppatori vi si oppongono perché sembra “lavoro extra.” I product manager vi si oppongono perché “rallenta la consegna.” Tutti vogliono muoversi velocemente e rompere cose, tranne che le cose che rompono sono la sanità mentale dei loro colleghi e la stabilità del sistema.
Contract First vi obbliga a pensare prima di codificare. Vi obbliga ad avere conversazioni sui tipi di dati, sui casi limite e sulla gestione degli errori prima che diventino incidenti di produzione. Impone disciplina in un’industria che venera il “muoversi velocemente.”
Ed è esattamente per questo che la maggior parte dei team rifiuta di adottarlo. La disciplina è difficile. Il caos è facile. Fino a quando non lo è più.
Quando NON Usare Contract First
Sarò onesto. Ci sono scenari in cui Code First ha senso:
- Prototipazione: State esplorando, non sapete ancora come sarà l’API, e avete bisogno di iterare velocemente. Va bene. Ma nel momento in cui va in produzione, scrivete quel contratto.
- Microservizi interni con gRPC: Se state già usando Protobuf (come ho sfogato in precedenza), il file
.protoè il vostro contratto. State già facendo Contract First senza chiamarlo così. - Progetti in solitaria: Se siete l’unico sviluppatore, frontend e backend, potete tenere il contratto nella vostra testa. Ma nel momento in cui si unisce qualcun altro, scrivetelo.
Per tutto il resto, specialmente team più grandi di una persona, specialmente progetti che si prevede durino più di un hackathon, Contract First è non negoziabile.
Conclusione
Ho visto progetti morire perché nessuno sapeva cosa dovesse fare l’API. Ho visto sviluppatori dimettersi perché passavano più tempo a fare reverse-engineering di endpoint non documentati che a costruire funzionalità. Ho visto interruzioni di produzione causate da “cambiamenti minori” che hanno rotto ogni client perché non c’era un contratto contro cui validare.
Contract First non è una metodologia. È una strategia di sopravvivenza. È la differenza tra costruire su un terreno solido e costruire su sabbie mobili bendati.
Scrivete il contratto. Generate il codice. Dormite la notte.
Oppure no. Continuate a vivere nel caos. Continuate a debugare collection di Postman alle 2 di notte. Continuate a spiegare al team mobile perché il campo ora si chiama transactionDate invece di txn_dt per la terza volta questo mese.
A voi la scelta.
