Best practice for partial updates in a RESTful ser

2019-01-04 15:37发布

I am writing a RESTful service for a customer management system and I am trying to find the best practice for updating records partially. For example, I want the caller to be able to read the full record with a GET request. But for updating it only certain operations on the record are allowed, like change the status from ENABLED to DISABLED. (I have more complex scenarios than this)

I don't want the caller to submit the entire record with just the updated field for security reasons (it also feels like overkill).

Is there a recommended way of constructing the URIs? When reading the REST books RPC style calls seem to be frowned upon.

If the following call returns the full customer record for the customer with the id 123

GET /customer/123
<customer>
    {lots of attributes}
    <status>ENABLED</status>
    {even more attributes}
</customer>

how should I update the status?

POST /customer/123/status
<status>DISABLED</status>

POST /customer/123/changeStatus
DISABLED

...

Update: To augment the question. How does one incorporate 'business logic calls' into a REST api? Is there an agreed way of doing this? Not all of the methods are CRUD by nature. Some are more complex, like 'sendEmailToCustomer(123)', 'mergeCustomers(123, 456)', 'countCustomers()'

POST /customer/123?cmd=sendEmail

POST /cmd/sendEmail?customerId=123

GET /customer/count 

Thanks Frank

标签: rest
10条回答
趁早两清
2楼-- · 2019-01-04 16:10

Things to add to your augmented question. I think you can often perfectly design more complicated business actions. But you have to give away the method/procedure style of thinking and think more in resources and verbs.

mail sendings


POST /customers/123/mails

payload:
{from: x@x.com, subject: "foo", to: y@y.com}

The implementation of this resource + POST would then send out the mail. if necessary you could then offer something like /customer/123/outbox and then offer resource links to /customer/mails/{mailId}.

customer count

You could handle it like a search resource (including search metadata with paging and num-found info, which gives you the count of customers).


GET /customers

response payload:
{numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}

查看更多
三岁会撩人
3楼-- · 2019-01-04 16:12

Use PUT for updating incomplete/partial resource.

You can accept jObject as parameter and parse its value to update the resource.

Below is the function which you can use as a reference :

public IHttpActionResult Put(int id, JObject partialObject)
{
    Dictionary<string, string> dictionaryObject = new Dictionary<string, string>();

    foreach (JProperty property in json.Properties())
    {
        dictionaryObject.Add(property.Name.ToString(), property.Value.ToString());
    }

    int id = Convert.ToInt32(dictionaryObject["id"]);
    DateTime startTime = Convert.ToDateTime(orderInsert["AppointmentDateTime"]);            
    Boolean isGroup = Convert.ToBoolean(dictionaryObject["IsGroup"]);

    //Call function to update resource
    update(id, startTime, isGroup);

    return Ok(appointmentModelList);
}
查看更多
聊天终结者
4楼-- · 2019-01-04 16:12

Regarding your Update.

The concept of CRUD I believe has caused some confusion regarding API design. CRUD is a general low level concept for basic operations to perform on data, and HTTP verbs are just request methods (created 21 years ago) that may or may not map to a CRUD operation. In fact, try to find the presence of the CRUD acronym in the HTTP 1.0/1.1 specification.

A very well explained guide that applies a pragmatic convention can be found in the Google cloud platform API documentation. It describes the concepts behind the creation of a resource based API, one that emphasizes a big amount of resources over operations, and includes the use cases that you are describing. Although is a just a convention design for their product, I think it makes a lot of sense.

The base concept here (and one that produces a lot of confusion) is the mapping between "methods" and HTTP verbs. One thing is to define what "operations" (methods) your API will do over which types of resources (for example, get a list of customers, or send an email), and another are the HTTP verbs. There must be a definition of both, the methods and the verbs that you plan to use and a mapping between them.

It also says that, when an operation does not map exactly with a standard method (List, Get, Create, Update, Delete in this case), one may use "Custom methods", like BatchGet, which retrieves several objects based on several object id input, or SendEmail.

查看更多
【Aperson】
5楼-- · 2019-01-04 16:14

Check out http://www.odata.org/

It defines the MERGE method, so in your case it would be something like this:

MERGE /customer/123

<customer>
   <status>DISABLED</status>
</customer>

Only the status property is updated and the other values are preserved.

查看更多
【Aperson】
6楼-- · 2019-01-04 16:15

For modifying the status I think a RESTful approach is to use a logical sub-resource which describes the status of the resources. This IMO is pretty useful and clean when you have a reduced set of statuses. It makes your API more expressive without forcing the existing operations for your customer resource.

Example:

POST /customer/active  <-- Providing entity in the body a new customer
{
  ...  // attributes here except status
}

The POST service should return the newly created customer with the id:

{
    id:123,
    ...  // the other fields here
}

The GET for the created resource would use the resource location:

GET /customer/123/active

A GET /customer/123/inactive should return 404

For the PUT operation, without providing a Json entity it will just update the status

PUT /customer/123/inactive  <-- Deactivating an existing customer

Providing an entity will allow you to update the contents of the customer and update the status at the same time.

PUT /customer/123/inactive
{
    ...  // entity fields here except id and status
}

You are creating a conceptual sub-resource for your customer resource. It is also consistent with Roy Fielding's definition of a resource: "...A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time..." In this case the conceptual mapping is active-customer to customer with status=ACTIVE.

Read operation:

GET /customer/123/active 
GET /customer/123/inactive

If you make those calls one right after the other one of them must return status 404, the successful output may not include the status as it is implicit. Of course you can still use GET /customer/123?status=ACTIVE|INACTIVE to query the customer resource directly.

The DELETE operation is interesting as the semantics can be confusing. But you have the option of not publishing that operation for this conceptual resource, or use it in accordance with your business logic.

DELETE /customer/123/active

That one can take your customer to a DELETED/DISABLED status or to the opposite status (ACTIVE/INACTIVE).

查看更多
Bombasti
7楼-- · 2019-01-04 16:18

You basically have two options:

  1. Use PATCH (but note that you have to define your own media type that specifies what will happen exactly)

  2. Use POST to a sub resource and return 303 See Other with the Location header pointing to the main resource. The intention of the 303 is to tell the client: "I have performed your POST and the effect was that some other resource was updated. See Location header for which resource that was." POST/303 is intended for iterative additions to a resources to build up the state of some main resource and it is a perfect fit for partial updates.

查看更多
登录 后发表回答