API di versioning


9

Supponiamo di avere un grande progetto supportato da una base API. Il progetto inoltre fornisce un'API pubblica che gli utenti finali possono usare.

A volte è necessario apportare modifiche alla base API che supporta il progetto. Ad esempio, è necessario aggiungere una funzione che richiede una modifica dell'API, un nuovo metodo o che richiede l'alterazione di uno degli oggetti o il formato di uno di quegli oggetti, passati a o dall'API.

Supponendo che si stiano utilizzando anche questi oggetti nell'API pubblica, anche gli oggetti pubblici cambieranno ogni volta che lo si fa, il che è indesiderabile poiché i client possono fare affidamento sugli oggetti API che rimangono identici perché il loro codice di analisi funzioni. (tosse client C ++ WSDL ...)

Quindi una potenziale soluzione è la versione dell'API. Ma quando diciamo "versione" dell'API, sembra che questo significhi anche la versione degli oggetti API oltre a fornire chiamate di metodo duplicate per ogni firma del metodo modificata. Quindi avrei un semplice oggetto clr per ogni versione del mio API, che di nuovo sembra indesiderabile. E anche se lo faccio, sicuramente non costruirò ogni oggetto da zero in quanto ciò finirebbe con enormi quantità di codice duplicato. Piuttosto, è probabile che l'API estenda gli oggetti privati ​​che stiamo usando per la nostra API di base, ma ci imbattiamo nello stesso problema perché le proprietà aggiunte sarebbero anche disponibili nell'API pubblica quando non dovrebbero esserlo.

Quindi qual è la sanità mentale che viene solitamente applicata a questa situazione? So che molti servizi pubblici come Git per Windows mantengono un'API con versione, ma ho difficoltà a immaginare un'architettura che supporti questo senza enormi quantità di codice duplicato che copre i vari metodi con versione e oggetti di input / output.

Sono consapevole del fatto che processi come il controllo delle versioni semantico tentano di migliorare la sanità mentale quando si verificano interruzioni dell'API pubblica. Il problema è più che sembra che molte o la maggior parte delle modifiche richiedano la rottura dell'API pubblica se gli oggetti non sono più separati, ma non vedo un buon modo per farlo senza duplicare il codice.


1
I don't see a good way to do that without duplicating code- La tua nuova API può sempre chiamare metodi nella tua vecchia API o viceversa.
Robert Harvey,

2
AutoMapper in soccorso, purtroppo sì, sono necessarie versioni distinte di ciascun contratto, non dimenticare che tutti gli oggetti a cui fa riferimento il tuo contratto fanno parte di quel contratto. Il risultato è che l'implementazione effettiva dovrebbe avere la propria versione singola dei modelli ed è necessario trasformare la versione singola nelle varie versioni del contratto. AutoMapper può aiutarti qui e rendere i modelli interni più intelligenti rispetto ai modelli di contratto. In assenza di AutoMapper, ho usato metodi di estensione per creare semplici traduzioni tra modelli interni e modelli di contratto.
Jimmy Hoffa,

Esiste una piattaforma / un contesto specifici per questo? (ad esempio, DLL, un'API REST, ecc.)
GrandmasterB

.NET, con interfaccia utente MVC e Webform, classi dll. Abbiamo sia un'API per il riposo che per il sapone.
Caso

Risposte:


6

Quando si mantiene un'API utilizzata da terze parti è inevitabile che sia necessario apportare modifiche. Il livello di complessità dipenderà dal tipo di cambiamento che si sta verificando. Questi sono i principali scenari che emergono:

  1. Nuova funzionalità aggiunta all'API esistente
  2. Funzionalità precedente obsoleta dall'API
  3. Funzionalità esistente nell'API che cambia in qualche modo

Nuova funzionalità aggiunta all'API esistente

Questo è lo scenario più semplice da supportare. L'aggiunta di nuovi metodi a un'API non dovrebbe richiedere alcuna modifica ai client esistenti. Questo è sicuro da implementare per i client che necessitano della nuova funzionalità in quanto non ha aggiornamenti per alcun client esistente.

Funzionalità precedente obsoleta dall'API

In questo scenario è necessario comunicare agli utenti esistenti dell'API che la funzionalità non sarà supportata a lungo termine. Fino a quando non si rilascia il supporto per la vecchia funzionalità (o fino a quando tutti i client non hanno eseguito l'aggiornamento alla nuova funzionalità) è necessario mantenere contemporaneamente la vecchia e la nuova funzionalità API. Se si tratta di una libreria fornita, la maggior parte delle lingue ha un modo per contrassegnare i vecchi metodi come obsoleti / deprecati. Se si tratta di un servizio di terze parti di qualche tipo, di solito è meglio avere endpoint diversi per la vecchia / nuova funzionalità.

Funzionalità esistente nell'API che cambia in qualche modo

Questo scenario dipenderà dal tipo di modifica. Se non è più necessario utilizzare i parametri di input, è possibile semplicemente aggiornare il servizio / libreria per ignorare i dati ora extra. In una libreria sarebbe avere il metodo sovraccarico che chiama internamente il nuovo metodo che richiede meno parametri. In un servizio ospitato l'endpoint ignora i dati aggiuntivi e può servire entrambi i tipi di client ed eseguire la stessa logica.

Se la funzionalità esistente deve aggiungere nuovi elementi richiesti, è necessario disporre di due endpoint / metodi per il proprio servizio / libreria. Fino all'aggiornamento dei client è necessario supportare entrambe le versioni.

Altri pensieri

Rather, the API is likely to extend the private objects we are using for our base API, but then we run into the same problem because added properties would also be available in the public API when they are not supposed to be.

Non esporre oggetti privati ​​interni tramite la tua libreria / servizio. Crea i tuoi tipi e mappa l'implementazione interna. Ciò consentirà di apportare modifiche interne e ridurre al minimo la quantità di aggiornamento che i client esterni devono eseguire.

The problem is more that it seems like many or most changes require breaking the public API if the objects aren't more separated, but I don't see a good way to do that without duplicating code.

L'API, che si tratti di un servizio o di una libreria, deve essere stabile nel punto di integrazione con i client. Più tempo impieghi per identificare quali input e output dovrebbero essere e tenerli come entità separate ti farà risparmiare un sacco di mal di testa lungo la strada. Rendi il contratto API la sua entità separata e mappalo alle classi che forniscono il vero lavoro. Il tempo risparmiato quando cambiano le implementazioni interne dovrebbe compensare di più il tempo extra impiegato per definire l'interfaccia extra.

Non visualizzare questo passaggio come "codice duplicato". Sebbene simili, sono entità separate che vale la pena creare. Mentre le modifiche alle API esterne richiedono quasi sempre una modifica corrispondente all'implementazione interna, le modifiche all'implementazione interna non devono sempre cambiare l'API esterna.

Esempio

Supponiamo che tu stia fornendo una soluzione per l'elaborazione dei pagamenti. Stai utilizzando PaymentProviderA per effettuare transazioni con carta di credito. Successivamente riceverai una tariffa migliore tramite il processore di pagamento di PaymentProviderB. Se l'API ha esposto i campi Indirizzo carta di credito / fattura del tuo tipo anziché la rappresentazione di PaymentProviderA, la modifica dell'API è 0 poiché l'interfaccia è rimasta la stessa (si spera comunque, se PaymentProviderB richiede dati non richiesti da PaymentProviderA, devi scegliere supportare entrambi o mantenere il tasso peggiore con PaymentProviderA).


Grazie per questa risposta molto dettagliata. Conoscete qualche esempio di progetti open source che potrei esaminare per sapere come i progetti esistenti hanno fatto questo? Mi piacerebbe vedere alcuni esempi concreti di come hanno organizzato il codice che consente loro di farlo senza semplicemente copiare il loro codice di costruzione POCO nei vari oggetti versione, poiché se si chiamano metodi condivisi, è necessario suddividerlo del metodo condiviso per poter modificare quell'oggetto per l'oggetto con versione.
Caso

1
Non sono a conoscenza di buoni esempi al di sopra della mia testa. Se avrò del tempo questo fine settimana, potrei creare un'app di test da lanciare su GitHub per mostrare come alcune di queste cose potrebbero essere implementate.
Phil Patterson,

1
La parte difficile è che da un livello elevato ci sono molti modi diversi per cercare di minimizzare l'accoppiamento tra client e server. WCF ha un'interfaccia denominata IExtensibleDataObject che consente di passare dal client i dati non inclusi nel contratto e di inviarli tramite cavo al server. Google ha creato Protobuf per la comunicazione tra sistemi (esistono implementazioni open source per .NET, Java, ecc.). Inoltre, ci sono molti sistemi basati sui messaggi che possono funzionare (supponendo che il processo possa essere eseguito in modo asincrono).
Phil Patterson,

Potrebbe non essere una cattiva idea mostrare un esempio specifico della duplicazione del codice che si sta tentando di rimuovere (come una nuova domanda di overflow dello stack) e vedere quali soluzioni escono dalla comunità. È difficile rispondere alla domanda in termini generali; quindi uno scenario specifico potrebbe essere migliore.
Phil Patterson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.