API CRUD: Comment spécifiez-vous les champs à mettre à jour?


9

Disons que vous avez une sorte de structure de données, qui est persistante dans une sorte de base de données. Pour simplifier, appelons cette structure de données Person. Vous êtes maintenant chargé de concevoir une API CRUD, qui permet à d'autres applications de créer, lire, mettre à jour et supprimer des Persons. Pour simplifier, supposons que cette API soit accessible via une sorte de service Web.

Pour les parties C, R et D de CRUD, la conception est simple. J'utiliserai une notation fonctionnelle de type C # - l'implémentation pourrait être SOAP, REST / JSON, ou autre chose:

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

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

Et la mise à jour? La chose naturelle à faire serait

void UpdatePerson(Identifier, Person);

mais comment voulez - vous préciser que les champs de Personla mise à jour?


Solutions que je pourrais trouver:

  • Vous pourriez toujours exiger qu'une personne complète soit transmise, c'est-à-dire que le client ferait quelque chose comme ça pour mettre à jour la date de naissance:

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

    Cependant, cela nécessiterait une sorte de cohérence transactionnelle ou de verrouillage entre le Get et la mise à jour; sinon, vous pourriez remplacer une autre modification effectuée en parallèle par un autre client. Cela rendrait l'API beaucoup plus compliquée. De plus, il est sujet aux erreurs, car le pseudo-code suivant (en supposant une langue client avec le support JSON)

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

    - qui semble correct - non seulement changerait DateOfBirth mais réinitialiserait également tous les autres champs à null.

  • Vous pouvez ignorer tous les champs qui le sont null. Cependant, comment feriez-vous alors une différence entre ne pas le changer DateOfBirth et le changer délibérément en null ?

  • Modifiez la signature en void UpdatePerson(Identifier, Person, ListOfFieldNamesToUpdate).

  • Modifiez la signature en void UpdatePerson(Identifier, ListOfFieldValuePairs).

  • Utilisez une fonction du protocole de transmission: Par exemple, vous pouvez ignorer tous les champs non contenus dans la représentation JSON de la personne. Cependant, cela nécessite généralement d'analyser le JSON vous-même et de ne pas pouvoir utiliser les fonctionnalités intégrées de votre bibliothèque (par exemple WCF).

Aucune des solutions ne me semble vraiment élégante. C'est sûrement un problème courant, alors quelle est la solution de meilleure pratique utilisée par tout le monde?


Pourquoi l'identifiant ne fait-il pas partie de la personne? Pour les Personinstances nouvellement créées qui ne sont toujours pas persistantes, et dans le cas où l'identifiant est décidé dans le cadre du mécanisme de persistance, laissez-le simplement à null. Quant à la réponse, JPA utilise un numéro de version; si vous lisez la version 23, lorsque vous mettez à jour l'élément si la version dans DB est 24, l'écriture échoue.
SJuan76

Autoriser et communiquer à la fois PUTet les PATCHméthodes. Lors de l'utilisation PATCH, seules les clés d'envoi doivent être remplacées, PUTl'objet entier étant remplacé.
Lode

Réponses:


8

Si vous n'avez pas de suivi des modifications comme exigence sur cet objet (par exemple, "L'utilisateur John a changé le nom et la date de naissance"), le plus simple serait de remplacer l'objet entier dans la base de données par celui que vous recevez du consommateur. Cette approche impliquerait un peu plus de données envoyées par câble, mais vous évitez de les lire avant la mise à jour.

Si vous avez une exigence de suivi d'activité. Votre monde est beaucoup plus compliqué et vous devrez concevoir comment stocker des informations sur les actions CRUD et comment les intercepter. C'est le monde dans lequel vous ne voulez pas plonger si vous n'avez pas une telle exigence.

Selon les valeurs prioritaires par des transactions distinctes, je suggère de faire des recherches sur le verrouillage optimiste et pessimiste . Ils atténuent ce scénario courant:

  1. L'objet est lu par user1
  2. L'objet est lu par user2
  3. Objet écrit par user1
  4. Objet écrit par user2 et modifications écrasées par user1

Chaque utilisateur a une transaction différente, donc SQL standard avec cela. Le verrouillage le plus courant est optimiste (également mentionné par @ SJuan76 dans les commentaires sur les versions). Votre version de votre dossier dans DB et pendant l'écriture, vous jetez d'abord un coup d'œil dans DB si les versions correspondent. Si les versions ne correspondent pas, vous savez que quelqu'un a mis à jour l'objet entre-temps et vous devez répondre avec un message d'erreur au consommateur à propos de cette situation. Oui, vous devez montrer cette situation à l'utilisateur.

Notez que vous devez lire l'enregistrement réel de la base de données avant de l'écrire (pour une comparaison de version de verrouillage optimiste), donc l'implémentation de la logique delta (écriture uniquement des valeurs modifiées) peut ne pas nécessiter de requête de lecture supplémentaire avant l'écriture.

La mise en logique delta dépend fortement du contrat avec le consommateur, mais notez que le plus simple pour le consommateur est de construire une charge utile complète au lieu du delta également.


2

Nous avons une API PHP au travail. Pour les mises à jour si un champ n'est pas envoyé dans l'objet JSON, il est défini sur NULL. Ensuite, il passe tout à la procédure stockée. La procédure stockée tente de mettre à jour chaque champ avec field = IFNULL (entrée, champ). Donc, si un seul champ se trouve dans l'objet JSON, seul ce champ est mis à jour. Pour vider explicitement un champ défini, nous devons avoir field = '', la base de données met alors à jour le champ avec soit une chaîne vide, soit la valeur par défaut de cette colonne.


3
Comment définissez-vous délibérément un champ sur null qui ne l'est pas déjà?
Robert Harvey

Tous les champs sont définis NOT NULL, donc par défaut les champs CHAR obtiennent '' et tous les champs entiers obtiennent 0.
Jared Bernacchi

1

Spécifiez la liste des champs mis à jour dans la chaîne de requête.

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

Implémentez la fusion des données stockées avec le modèle du corps de la demande:

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;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.