Come decidere la granularità per le API RESTful

5

La maggior parte di noi conosce SOLID e nel corso degli anni ha capito quanto può essere utile quando dobbiamo cambiare.

Basato su S & I parti di SOLID e dall'esperienza che ho usato per progettare i miei servizi RESTful HTTP a grana fine come avrei potuto. Ad esempio, in un semplice sistema di gestione dei contatti, vorrei creare ed esporre questi endpoint:

  • createContact firstName = x &? LastName = y
  • addPhoneToContact contactid = x &? Telefono = y
  • addEmailToContact contactid = x &? E-mail = y
  • associateContactWithGroup contactid = x &? GroupId = y
  • removePhoneFromContact contactid = x &? PhoneId = y
  • updateContactName contactid = x &? FirstName = y & lastName = z

Tuttavia, di recente abbiamo discusso sulla granularità dei nostri servizi RESETful e un collega ha proposto di vedere Contatto di Google come esempio e modello.

Con mia sorpresa, ho visto che aggiornano i contatti in modo molto complesso e a grana grossa, in una semplice API.

Dobbiamo ammettere che Google è il gigante di Internet e che non fanno cose senza sapere cosa fanno.

Se si trattasse di un sito web arbitrario, non li prenderei nemmeno in considerazione, e probabilmente sosterrei che i loro sviluppatori non hanno familiarità con il concetto di abbattere le cose (divide e conquista / analisi / WBS / SOLID / ...) . Ma Google, è completamente diverso.

Ora sono bloccato sul motivo per cui lo hanno fatto, e quale approccio è più gestibile e migliore per creare API. Una grande singola API che aggiorna / crea un modello complesso o molte piccole API che aggiornano / creano piccole parti di quel modello.

Questa è una sfida per i carrelli della spesa, per i contatti, per la gestione del corso di formazione, per articoli contabili e quasi molti altri casi.

So che questo potrebbe essere soggettivo, ma sto cercando di trovare ragioni oggettive dietro ogni approccio, in modo che possiamo decidere con più conoscenza.

    
posta Saeed Neamati 09.03.2017 - 13:49
fonte

3 risposte

6

Questo confina con una risposta religiosa, ma ....

Gli esempi forniti nella domanda non sono RESTful, per me. createContact è un verbo, non un nome e non specifica il tipo di richiesta. Il paradigma REST suggerirebbe:

POST /contact - data={firstname: 'blah', lastname: 'blah'}

Il removePhoneFromContact?contactId=x&phoneId=y diventerebbe:

DELETE /contact/x/phone/y o %codice% se phoneId è globalmente unico

Allo stesso modo, le operazioni di modifica possono essere eseguite da un PUT o un PATCH. PATCH potrebbe essere utile dove si desidera modificare un solo campo e non è necessario inviare la risorsa completa. Cose come gli etags e le intestazioni dell'ultima modifica vengono utilizzate per garantire la coerenza.

Google ha anche molte considerazioni che noi Common Folk non fanno. Il risparmio di pochi punti percentuali nell'utilizzo della rete potrebbe essere molto più importante per loro rispetto alla facilità di comprensione di un'API.

    
risposta data 09.03.2017 - 15:53
fonte
3

Penso che forse i contatti siano un cattivo esempio dal punto di vista di SOLID. A prima vista, "Modifica dei contatti" sembra una singola responsabilità.

Se immaginiamo il caso più generale anche se penso che tu abbia una buona domanda. Quali fattori oggettivi possono prendere in considerazione quando si decide di dividere una responsabilità?

Penso ci siano un paio di considerazioni pratiche.

  1. Quanto è grande il tuo oggetto / radice aggregata? dobbiamo tenere questa cosa in memoria e mandarla sul filo. Ad un certo punto sarà troppo grande per fare quelle cose in modo efficace

  2. Con che frequenza lo cambiamo. I cambiamenti stanno andando a scontrarsi? se aggiungo indirizzi migliaia di volte al secondo da più client, provare a cambiare l'intero oggetto ogni volta è probabile che crei collisioni e sia sub ottimale rispetto a solo AddAddress (indirizzo).

  3. Come immagazziniamo l'oggetto. Se la memoria sottostante è un db no-sql che conserva l'intero oggetto, l'efficienza di esporre AddAddress si ferma a questo livello. Se hai un db relazionale che può aggiungere indirizzi a una tabella senza guardare il resto dei dati, vuoi essere in grado di offrire quel valore lungo tutta la catena.

Suppongo che Google stia utilizzando un db no-sql per l'archiviazione dei contatti, che gli oggetti di contatto siano piccoli rispetto alla larghezza di banda e alla memoria e che solo il proprietario di quel contatto probabilmente eseguirà aggiornamenti, con una percentuale di clic del pulsante umano.

    
risposta data 09.03.2017 - 15:26
fonte
2

Come altri hanno sottolineato, un'API RESTful dovrebbe esporre risorse (nomi) piuttosto che azioni. Vedi Trasferimento dello stato di rappresentanza (Wikipedia).

Ma capisco che la tua domanda riguardi la granularità delle risorse esposte dall'API.

Prendiamo ad esempio il tuo contatto e la sua proprietà di posta elettronica. Inizieremo creando un contatto:

POST /contact 
{"name": "John Doe"}

(Si prega di perdonare una tangente poiché non è completamente correlata alla domanda, ma suggerisco di non dividere i nomi in first e last a meno che non sia assolutamente necessario ed ecco perché ).

Restituisce un nuovo contact_id di 17 .

Quindi ora la vera domanda è , aggiungiamo un numero di telefono a John in questo modo?

PATCH /contact/17
{"phone": "555-5555"}

... o come questo?

POST /contact/17/phone
555-5555

Rifletti il modello di dati?

Penso che un determinante possa essere il modello di dati sottostante.

Se si dispone di uno schema di database dei contatti simile a questo:

=====================================
contact
=====================================
contact_id    name          phone
-------------------------------------
17            john doe      555-5555

Quindi penso che la singola risorsa /contact abbia il significato più immediato.

Ma se lo schema del tuo database consente una relazione uno-a-molti con numeri di telefono come questo:

========================
contact
========================
contact_id    name      
------------------------
17            john doe  



===================================
phone
===================================
phone_id    contact_id    number
-----------------------------------
243         17            555-5555

Quindi la risorsa più granulare, /contact/17/phone , sembra improvvisamente ragionevole. (Dopo che è stato creato, questo numero di telefono specifico sarà a /contact/17/phone/243 su cui potresti agire con i verbi HTTP PUT o DELETE).

Naturalmente, le risorse API non devono (e spesso non dovrebbero) rispecchiare il modello dati sottostante. Entrambi gli schemi di database potrebbero riflettersi perfettamente con la struttura dell'API. Ma può essere

Altre considerazioni:

  • L'API può suggerire come dovrebbe essere usato dal modo in cui è strutturato. /contacts e /contacts/{id}/phone potrebbero consentire la stessa funzionalità, ma danno un'impressione diversa al consumatore dell'API finale!
  • Più API granulari possono in teoria permettervi di creare servizi più efficienti, consentendo di scrivere bit di codice molto specifici a scapito del codice più . /contacts/{id}/phone può essere scritto in modo molto semplice - ma ora sei addossato al sovraccarico di scrivere e mantenere (e documentare !) questa risorsa per tutta la durata dell'API
  • Puoi aggiungere risorse più granulari mantenendo la compatibilità con le versioni precedenti, ma non puoi portarle via! Quindi, se sei sulla recinzione, considera l'avvio di grana grossa e l'aggiunta di grana più fine, se necessario!

Anche in qualche modo tangente alla tua domanda attuale è come affrontare questa complicata relazione in REST:

  • associateContactWithGroup contactid = x &? GroupId = y

Puoi consentire a /contact/17 di assumere una proprietà {"group": "34"} in un aggiornamento. Oppure puoi creare una risorsa /contact/17/group . (Questo potrebbe servire una relazione one-to-one o many-to-many con i gruppi, che sembrerebbe molto simile.)

La cosa interessante è che potresti anche voler esporre questo dal punto di vista dei gruppi con risorse come /group/34/contacts . Non c'è una risposta sbagliata, ma considera di avere poche risorse quante ne puoi ottenere per iniziare. Puoi sempre aggiungere altro in un modo compatibile con le versioni precedenti!

    
risposta data 09.03.2017 - 17:32
fonte

Leggi altre domande sui tag