Regola
Evita di violare i contratti delle API pubbliche.
Le modifiche agli endpoint delle API pubbliche che interrompono
le richieste dei client esistenti costituiscono modifiche strutturali incompatibili.
Considera il contratto API come una promessa — modificarlo
dopo che i client hanno iniziato a farvi affidamento compromette il loro codice.
Linguaggi supportati: PHP, Java, C#, Python, JavaScript, TypeScriptIntroduzione
Le API pubbliche sono contratti tra il tuo servizio e i suoi consumer. Una volta che i client dipendono dal formato di richiesta, dalla struttura di risposta o dal comportamento di un endpoint, modificarli compromette il loro codice. Le modifiche non retrocompatibili costringono tutti i client ad aggiornarsi simultaneamente, il che risulta spesso impossibile quando non si ha il controllo diretto sui client stessi. Le app per dispositivi mobili non possono essere aggiornate forzatamente, le integrazioni di terze parti richiedono tempo per la migrazione e i sistemi legacy potrebbero non essere mai aggiornati.
Perché è importante
Disservizio per i client e perdita di fiducia: Le modifiche che rompono la compatibilità delle API causano errori immediati nelle applicazioni client in produzione. Gli utenti riscontrano errori, perdita di dati o interruzioni totali del servizio. Ciò danneggia il rapporto di fiducia tra i fornitori di API e i consumer, oltre a violare il contratto implicito secondo cui le API stabili devono rimanere tali.
Costi di coordinamento: Coordinare modifiche distruttive tra più team di sviluppo client è costoso e lento. Ciascun team necessita di tempo per aggiornare il codice, testare le modifiche e procedere al rilascio. Per le API pubbliche con client sconosciuti (app mobili, integrazioni di terze parti), il coordinamento risulta impossibile.
Proliferazione delle versioni: Modifiche non retrocompatibili gestite in modo inadeguato portano al mantenimento simultaneo di più versioni delle API. Ciascuna versione richiede percorsi di codice, test, documentazione e correzioni di bug separati, moltiplicando esponenzialmente l'onere di manutenzione.
Esempi di codice
❌ Non conforme:
// Version 1: Original API
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json({ id: user.id, name: user.name });
});
// Version 2: Breaking change - renamed field
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json({
id: user.id,
fullName: user.name // Breaking: 'name' renamed to 'fullName'
});
});
Perché è errato: Ridenominare name in fullName interrompe il funzionamento di tutti i client esistenti che si aspettano il campo name. Il codice del client che accede a response.name riceverà undefined, generando errori. Questa modifica costringe tutti i client ad aggiornarsi contemporaneamente, pena il fallimento.
✅ Conforme:
// Version 2: Additive change - keeps old field, adds new
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json({
id: user.id,
name: user.name, // Keep for backward compatibility
fullName: user.name // Add new field (deprecated 'name')
});
});
// Or use API versioning
app.get('/api/v2/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json({ id: user.id, fullName: user.name });
});
Perché questo è importante: Mantenere il vecchio name il campo preserva la compatibilità con le versioni precedenti aggiungendo fullName per i nuovi client. In alternativa, creare un nuovo endpoint con versione (/api/v2/) consente di introdurre modifiche non retrocompatibili senza compromettere i client esistenti che utilizzano ancora /api/v1/.
Conclusione
Evolvi le API attraverso modifiche additive: aggiungi nuovi campi, nuovi endpoint o parametri opzionali. Quando le modifiche che rompono la compatibilità sono inevitabili, utilizza il versionamento delle API per eseguire la vecchia e la nuova versione in parallelo. Depreca i vecchi campi fornendo tempistiche chiare e guide di migrazione prima di rimuoverli.

