Progettazione della parte CRUD di un'API HTTP

5

Questa domanda è nel contesto delle applicazioni basate sul web. Un server web esposizione di un API HTTP per i client (ad esempio, l'esecuzione in un browser, ma non necessariamente). Di solito il server web sarebbe connesso a qualche tipo di DataStorage.

La mia preoccupazione è l'API esposta dal server HTTP e come progettarla.

Trovo che, spesso, una grande parte di API esposta al cliente sia solo una Interfaccia CRUD al datastorage. Ad esempio, l'api quasi sempre contiene cose come:

- get_all_users()
- get_this_user(id)
- create_new_user(name, email, phone, password)
- update_user(id, name?, email?, phone?, password?)
- delete_user(id)

Questo non è tutto ciò che fa l'API, ma è qualcosa che è comune a molto di API con cui ho lavorato.

A seconda di ciò di cui il cliente ha bisogno, spesso finisco sempre di più cose nell'API come queste:

- get_user_by_email(email)
- get_all_users_along_with_number_of_lincenses_for_each()
- delete_all_these_users_at_once(array [])
- get_users_whose_profile_is(profile_name)
- get_names_of_users_who_are_admin_profiles()
- .... etc

In altre parole, il client spesso ha bisogno di fare join, filtrare e ordinare il file dati in tutti i modi.

Che mi ha portato a utilizzare API eccessivamente gonfie (proprio come quella sopra) supportare tutti i tipi di opzioni per filtrare, ordinare e unirsi a dati da altre tabelle.

L'API risultante è troppo complessa per essere utilizzata e appresa da entrambi i client e per il server da mantenere e testare.

(IMO l'abl di Zabbix HTTP soffre di questa sindrome).

Domande:

  • Hai esperienza simile?

  • Hai qualche indicazione su come trovare un buon equilibrio tra Completezza e complessità dell'API.

PS. Sono a conoscenza di REST, ma non trovo che risolva il problema, l'api fornito è ancora molto semplice e non risolve il problema get_users_along_with_number_of_licenses () o il get_names_of_users_which_are_admin () chiamate api.;

    
posta cerendata 07.08.2015 - 12:13
fonte

3 risposte

5

Questo è un classico dilemma dell'architettura delle API, indipendentemente dal fatto che sia fornito da un servizio web, o collegando una libreria, o semplicemente facendo parte di un codice base che viene creato insieme a tutto il resto. Si riduce a se preferisci molte funzioni che ognuna fa una cosa specifica o se hai meno funzioni parametrizzate.

Poiché le chiamate Web sono più "costose" in termini di tempo, molti progettisti di API Web scelgono le chiamate meno numerose. Un'interfaccia CRUD su HTTP fornirebbe operazioni di lettura (GET), scrittura / creazione (PUT / POST) ed eliminazione (DELETE), con tutti i parametri necessari forniti nel corpo della richiesta o codificati come parametri nell'URL (QueryString).

Per affrontare il tuo elenco sempre crescente di modi per filtrare e organizzare le tue operazioni di lettura, dovresti definire un insieme più ricco di input, che può essere complesso e strutturato come desideri, poiché HTTP fornirà tutto ciò che gli chiedi. La maggior parte delle persone progetta i propri parametri come coppie nome / valore, o come oggetti Json o come documenti xml. I parametri possono includere criteri di filtro e opzioni che indicano quali dati si desidera che la chiamata restituisca.

Atlassian fa questo con le loro API REST per JIRA e Confluence (e altri). Forniscono URL (chiamate) per ottenere tutti i problemi o un problema o alcuni problemi basati su un filtro o una query. Ogni chiamata GET supporta un parametro (in questo caso, chiamato expand , che consente al chiamante di chiedere che alcuni dati di ritorno facoltativi siano inclusi nella risposta.

Per quanto riguarda l'ordinamento, se il servizio restituirà molti dati o dati frammentati (paginati), è bello progettare la chiamata con un parametro sort per rendere la vita più semplice per il chiamante. Per elenchi di dati più piccoli, potrebbe essere più semplice fornire i dati in un solo ordine e lasciare che il client li ordini come preferiscono.

REST è attualmente popolare perché è molto disaccoppiato tra il chiamante e il provider, che offre estrema flessibilità. Potresti preferire qualcosa come XMLRPC, che assomiglia più alle chiamate di funzioni al codice cliente. Puoi comunque fornire una chiamata che accetta molti parametri o un grande parametro simile ad un oggetto. Dipende ancora da te.

Fai un favore a te stesso e ai tuoi utenti e assicurati di documentare accuratamente la tua API e rendila facilmente disponibile. Negli ultimi tempi, la tendenza nei servizi Web è quella di allontanarsi dai vincoli (come in SOAP e XMLRPC), il che rende più difficile per i compilatori e gli IDE aiutare a sapere come effettuare una chiamata. La documentazione completa e accessibile sarà tua amica.

    
risposta data 07.08.2015 - 14:04
fonte
1

Ho fatto cose come REST, che, come dici tu, non risolve tutti i problemi, anche se almeno alcuni di essi possibilmente.

In pratica le funzionalità di ricerca che ho implementato utilizzando i parametri URL invece di avere un singolo URL per ognuno di essi. Quindi il tuo get_user_by_email sarebbe solo

/[email protected]

Simile puoi gestire i dati restituiti per cose come get_all_users_along_with_number_of_lincenses_for_each

/users?fields=number_licenses

O anche restituire dati annidati come

/users?includes=licenses

Possono anche essere diversi:

/users?includes=licenses,orders

che restituirebbe un oggetto JSON nidificato con gli utenti e tutti i record di licenza.

o per il tuo get_names_of_users_who_are_admin_profiles come questo:

/users?admin=true&fields=id,name

Il metodo get_users_whose_profile_is sarebbe una semplice rotta nidificata:

/profiles/{profile_id}/users

Naturalmente questo non risolve completamente il problema di complessità, ma almeno riduce la quantità di endpoint che l'utente deve accedere e imparare. Tuttavia ha bisogno di sapere quali campi esistono e come interrogarli (abbiamo anche alcune funzionalità di ricerca full text per alcune risorse).

Un altro problema qui potrebbero essere le autorizzazioni, poiché alcuni utenti potrebbero essere autorizzati a cercare e richiedere diversi tipi di dati. Soprattutto quali campi è autorizzato a richiedere può essere problematico. Almeno con gli endpoint ridotti, il primo livello di autorizzazioni è più semplice (l'utente può richiedere / gli utenti / o non può farlo.

Quanto è facile implementarlo può dipendere dal tuo stack tecnologico. Usiamo Ruby on Rails che consente di scrivere alcuni metodi generici che semplificano l'elaborazione di cose come fields=name,id,street,city o includes=orders,licenses (ma fai attenzione, anche in questo caso devi assicurarti che le autorizzazioni siano gestite correttamente)

    
risposta data 07.08.2015 - 13:00
fonte
1

Ti suggerisco di 1) imparare quali sono le altre API REST e 2) considerare OData

Adoro Odata perché supporta tutti gli scenari elencati in una singola chiamata API. Sono di parte, però, perché in C # è possibile implementare un server Odata in poche righe di codice se si utilizza Entity Framework. Esistono anche librerie lato client in grado di formattare gli URL OData.

Permettetemi di applicare alcuni dei vostri esempi di query a OData:

  • get_user_by_email (e-mail)

link $ filter = email eq '[email protected]'

  • get_all_users_along_with_number_of_lincenses_for_each ()

link $ filter = email eq '[email protected]'/Licenses

  • get_names_of_users_who_are_admin_profiles ()

link $ filter = admin eq true $ select = Nome

Nel complesso, OData fondamentalmente mappa tutto ciò che troveresti in una clausola di selezione SQL nella stringa Query. L'unica lamentela a riguardo è che è un po ' troppo potente, quindi è necessario lavorare sul lato server per assicurarsi che qualcuno non faccia una query complicata per un milione di record. Limita il numero di risultati, limita il tempo di esecuzione della query, ecc.

Con il tuo esempio di cancellazione, ci sono alcuni modi comuni per passare gli array. Se leggi la documentazione su Swagger puoi ottenere un'idea dei diversi modi in cui le persone tendono a passare gli array come argomenti. Swagger elenca valori separati da virgola, separati da spazi, separati da tabulazioni, separati da pipe e ripetuti più volte il parametro. CSV è l'impostazione predefinita, quindi mi dice che le persone tendono a usarlo molto. ASP.NET MVC associa in modo nativo l'ultimo esempio a un array. Quindi considera:

  • delete_all_these_users_at_once (array [])

HTTP DELETE link o HTTP DELETE link

    
risposta data 07.08.2015 - 18:12
fonte