API CRUD: come si specificano i campi da aggiornare?

9

Supponiamo che tu abbia una sorta di struttura dati, che viene mantenuta in una sorta di database. Per semplicità, chiamiamo questa struttura dati Person . Ora hai il compito di progettare un'API CRUD, che consente ad altre applicazioni di creare, leggere, aggiornare ed eliminare Person s. Per semplicità, supponiamo che questa API sia accessibile tramite una sorta di servizio Web.

Per le parti C, R e D di CRUD, il design è semplice. Userò la notazione funzionale di tipo C # - l'implementazione potrebbe essere SOAP, REST / JSON, o qualcos'altro:

class Person {
    string Name;
    DateTime? DateOfBirth;
    ...
}

Identifier CreatePerson(Person);
Person GetPerson(Identifier);
void DeletePerson(Identifier);

E riguardo l'aggiornamento? La cosa naturale da fare sarebbe

void UpdatePerson(Identifier, Person);

ma come specificheresti quali campi di Person per aggiornare?

Soluzioni che potrei trovare:

  • Potresti sempre richiedere una persona completa da passare, cioè il client farebbe qualcosa del genere per aggiornare la data di nascita:

    p = GetPerson(id);
    p.DateOfBirth = ...;
    UpdatePerson(id, p);
    

    Tuttavia, ciò richiederebbe una sorta di coerenza transazionale o di blocco tra Get e l'aggiornamento; altrimenti, potresti sovrascrivere qualche altro cambiamento fatto in parallelo da qualche altro client. Ciò renderebbe l'API molto più complicata. Inoltre, è soggetto a errori, dal seguente pseudo-codice (presupponendo una lingua client con supporto JSON)

    UpdatePerson(id, { "DateOfBirth": "2015-01-01" });
    

    - che sembra corretto - non cambierebbe solo DateOfBirth ma ripristinerebbe anche tutti gli altri campi su null.

  • Potresti ignorare tutti i campi che sono null . Tuttavia, come faresti allora una differenza tra non cambiare DateOfBirth e modificarlo deliberatamente in null ?

  • Cambia la firma in void UpdatePerson(Identifier, Person, ListOfFieldNamesToUpdate) .

  • Cambia la firma in void UpdatePerson(Identifier, ListOfFieldValuePairs) .

  • Utilizza alcune funzionalità del protocollo di trasmissione: ad esempio, puoi ignorare tutti i campi non contenuti nella rappresentazione JSON della persona. Tuttavia, di solito è necessario analizzare personalmente il JSON e non essere in grado di utilizzare le funzionalità incorporate della libreria (ad esempio WCF).

Nessuna delle soluzioni sembra davvero elegante per me. Sicuramente, questo è un problema comune, quindi qual è la soluzione best practice utilizzata da tutti?

    
posta Heinzi 13.09.2015 - 12:58
fonte

3 risposte

8

Se non si dispone di tracciamento delle modifiche come requisito su questo oggetto (ad esempio "Utente Giovanni ha cambiato nome e data di nascita"), il più semplice sarebbe quello di sovrascrivere l'intero oggetto in DB con quello che si riceve dal consumatore. Questo approccio comporterebbe un numero leggermente maggiore di dati inviati tramite filo, ma si evita la lettura prima dell'aggiornamento.

Se hai esigenze di monitoraggio delle attività. Il tuo mondo è molto più complicato e dovrai progettare come memorizzare informazioni sulle azioni CRUD e su come intercettarle. Questo è il mondo in cui non vuoi immergerti se non hai tale esigenza.

Come per la sostituzione dei valori con transazioni separate, suggerirei di effettuare ricerche intorno a ottimistico e blocco pessimistico . Riducono questo scenario comune:

  1. L'oggetto viene letto dall'utente1
  2. L'oggetto viene letto dall'utente2
  3. Oggetto scritto dall'utente1
  4. Oggetto scritto dall'utente2 e modifiche sovrascritte dall'utente1

Ogni utente ha una transazione diversa, quindi SQL standard con questo. Il più comune è locking ottimistico (citato anche da @ SJuan76 nel commento sulle versioni). La tua versione del tuo record in DB e durante la scrittura ti dà prima un'occhiata in DB se le versioni corrispondono. Se le versioni non corrispondono, sai che qualcuno ha aggiornato l'oggetto nel frattempo e devi rispondere con un messaggio di errore al consumatore in merito a questa situazione. Sì, devi mostrare questa situazione all'utente.

Si noti che è necessario leggere il record effettivo dal DB prima di scriverlo (per il confronto della versione di blocco ottimistico), quindi l'implementazione della logica delta (scrittura solo dei valori modificati) potrebbe non richiedere ulteriori query di lettura prima di scrivere.

L'impostazione della logica delta dipende in larga misura dal contratto con il consumatore, ma si noti che per il consumatore è più semplice costruire un payload completo invece del delta.

    
risposta data 13.09.2015 - 15:31
fonte
2

Abbiamo un'API PHP al lavoro. Per gli aggiornamenti se un campo non viene inviato nell'oggetto JSON viene impostato su NULL. Quindi passa tutto alla stored procedure. La stored procedure tenta di aggiornare ogni campo con field = IFNULL (input, campo). Quindi se solo 1 campo si trova nell'oggetto JSON, solo quel campo viene aggiornato. Per svuotare esplicitamente un campo impostato dobbiamo avere field = '', quindi il DB aggiorna il campo con una stringa vuota o il valore predefinito di quella colonna.

    
risposta data 13.09.2015 - 18:29
fonte
1

Specifica l'elenco dei campi aggiornati in Stringa di query.

PUT /resource/:id?fields=name,address,dob Body { //resource body }

Implementare unire i dati memorizzati con il modello dal corpo della richiesta:

private ResourceModel MergeResourceModel(ResourceModel original, ResourceModel updated, List<string> fields)
{
    var comparer = new FieldComparer();

    foreach (
            var item in
            typeof (ResourceModel).GetProperties()
                    .Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof (JsonIgnoreAttribute))))
    {
        if (fields.Contains(item.Name, comparer))
        {
            var property = typeof (ResourceModel).GetProperty(item.Name);
            property.SetValue(original, property.GetValue(updated));
        }
    }

    return original;
}
    
risposta data 14.09.2015 - 03:24
fonte

Leggi altre domande sui tag