CRUD API:如何指定要更新的字段?


9

假设您有某种数据结构,该数据结构保留在某种数据库中。为简单起见,我们将此数据结构称为Person。现在,您要负责设计CRUD API,该API允许其他应用程序创建,读取,更新和删除Person。为简单起见,让我们假定通过某种Web服务访问此API。

对于CRUD的C,R和D部分,设计很简单。我将使用类似C#的功能符号-实现可以是SOAP,REST / JSON或其他方式:

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

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

那更新呢?自然要做的是

void UpdatePerson(Identifier, Person);

但你会如何指定哪些领域Person要更新?


我可以提出的解决方案:

  • 您始终可以要求通过一个完整的“人员”,即客户将执行以下操作来更新出生日期:

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

    但是,这将需要某种事务上的一致性或在Get和Update之间锁定;否则,您可能会覆盖其他客户端并行进行的其他更改。这会使API更加复杂。此外,由于下面的伪代码(假设客户端语言支持JSON),因此容易出错。

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

    - 看起来正确-不仅会更改DateOfBirth,而且会将所有其他字段重置为null。

  • 您可以忽略所有的字段null。但是,您将如何在不更改 DateOfBirth有意将其更改为null之间做出区别?

  • 将签名更改为void UpdatePerson(Identifier, Person, ListOfFieldNamesToUpdate)

  • 将签名更改为void UpdatePerson(Identifier, ListOfFieldValuePairs)

  • 使用传输协议的某些功能:例如,您可以忽略Person的JSON表示中未包含的所有字段。但是,这通常需要您自己解析JSON,并且无法使用库的内置功能(例如WCF)。

在我看来,所有解决方案都不是很优雅。当然,这是一个普遍的问题,那么每个人使用的最佳实践解决方案是什么?


为什么标识符不是人的一部分?对于Person仍未持久的新创建实例,并且在将标识符确定为持久性机制的一部分的情况下,只需将其保留为null。至于答案,JPA使用版本号。如果阅读版本23,则更新项目(如果DB中的版本为24)将导致写入失败。
SJuan76

允许并交流PUTPATCH方法。使用时PATCH,只应替换发送键,并且替换PUT整个对象。
Lode

Answers:


8

如果您没有根据该对象的要求跟踪更改(例如,“用户John更改名称和出生日期”),最简单的方法就是使用从消费者那里收到的对象覆盖数据库中的整个对象。这种方法将涉及通过有线发送更多的数据,但是您要避免在更新之前先读取数据。

如果您有活动跟踪要求。您的世界要复杂得多,您将需要设计如何存储有关CRUD行为的信息以及如何拦截它们。如果您没有这样的要求,那就是您不想涉足的世界。

对于通过单独的事务覆盖值,我建议围绕乐观锁定悲观锁定进行研究。它们缓解了这种常见情况:

  1. 用户1读取对象
  2. 用户2读取对象
  3. user1编写的对象
  4. 由user2写入的对象和由user1覆盖的更改

每个用户都有不同的事务,因此与此相关的是标准SQL。最常见的是乐观锁定(@ SJuan76在有关版本的评论中也提到过)。您的版本是您在数据库中记录的版本,在写入过程中,如果版本匹配,则首先要查看数据库。如果版本不匹配,则您知道有人在此同时更新了该对象,并且您需要针对此情况向消费者发出错误消息以进行响应。是的,您必须向用户显示这种情况。

请注意,您需要在写入数据库之前先读取数据库的实际记录(用于比较乐观的锁定版本),因此实现增量逻辑(仅写入更改的值)可能不需要在写入之前进行其他读取查询。

引入增量逻辑在很大程度上取决于与使用者的合同,但请注意,对于使用者而言,最容易的是构造完整的有效负载而不是增量。


2

我们正在使用PHP API。对于更新,如果未在JSON对象中发送字段,则将其设置为NULL。然后,它将所有内容传递给存储过程。该存储过程尝试使用field = IFNULL(input,field)更新每个字段。因此,如果JSON对象中只有1个字段,则只会更新该字段。要显式清空设置的字段,我们必须具有field =”,然后DB用空字符串或该列的默认值更新该字段。


3
您如何故意将尚未为null的字段设置为null?
罗伯特·哈维

所有字段都设置NOT NULL,所以默认情况下,煤焦领域得到“”和所有的整数字段得到0。
贾里德Bernacchi

1

在查询字符串中指定更新的字段列表。

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

从请求主体实现合并存储的数据与模型:

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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.