1

I have a resource which represents a set of physical devices.

Calling GET v1/devices/ yields the following result:

[
  {
    "MacAddress": "DD-22-33-15-15-66",
    "Name": "Test Device",
    "State": "Approved"
  },
  {
    "MacAddress": "E5-21-56-44-11-B6",
    "CompanyId": "Another Test Device",
    "State": "Pending"
  }
]

A device has a state (an important attribute) which can only be pending or approved, and so the following GET requests are also available:

  • GET v1/devices/pending : Retrieves all pending devices
  • GET v1/devices/approved : Retrieves all approved devices

You can also get an individual device from the resource using GET v1/devices/EF-55-33-44-54-61

I now want to be able to update only the state of a device from Pending to Approved.

Would the following PATCH call make sense?

PATCH v1/devices/EF-55-33-44-54-61/approve

From some reading, it would seem the proper way to do it would be something like this:

[
  {"replace": "/state", "value": "approved"}
]

But this seems too flexible for such a specific update. I never want other values to be updated and I also don't want the state to be changed in any other way.

Dave New
  • 38,496
  • 59
  • 215
  • 394

3 Answers3

1

I would perform the PATCH against the URI of the object being patched:

PATCH v1/devices/EF-55-33-44-54-61

There is no convention saying that the PATCH method in your Web API Controller has to allow patching for all properties on the device object. If you only support an update to that one property, that is OK.

While the longer JSON above seems a bit excessive, it also means that if something changes later and you do need to PATCH more than one property, you now only need to update your controller to allow it and the JSON stays the same (albeit with potentially more than one replacement being done in a single call).

jmsb
  • 4,846
  • 5
  • 29
  • 38
  • The difference is that I am not updating the `state` property with a string value from user. The user is merely wanting to "approve" a device (which will just happen to change the property to `approved`). There doesn't even need to be a state property, but I still want devices to be approved. If I want to allow patch updates to properties, then I will open up a typical PATCH method. – Dave New Nov 14 '14 at 05:46
  • If you're going to make special endpoints such as 'PATCH v1/devices/EF-55-33-44-54-61/approve' I would just leave the payload empty and have the endpoint's controller method just change the flag without looking at any input. It's a convenience endpoint, and the generic patch JSON you included in your OP only makes sense if you want to implement the typical PATCH method. My answer was mainly addressing the (un)likely future where you want to patch other members. – jmsb Nov 14 '14 at 14:00
  • Yes, this is indeed a convenience method, and the controller does not consider the input. Input is purely for routing purposes. – Dave New Nov 18 '14 at 11:52
1

First off, you shouldn't use this URL pattern to filter the returned devices. You're using the pattern below, but IMHO, it's not the best approach here.

GET v1/devices/<approved|pending>

Instead, if you want to filter or limit the returned devices using as filter one or more of its attributes, you should use the QueryString like this:

GET v1/devices?status=<approved|pending>

Remember that your URIs should address only resources and, in my opinion, "approved" isn't a resource. In fact, it's a state of this resource. In this case, use the QueryString to filter or limit the resources you want to work upon. Moving on to your main question...

Would the following PATCH call make sense? PATCH v1/devices/EF-55-33-44-54-61/approve

Yes and no! :) You can use the same approach that Github uses. On Github, if you want to "star" a gist through their API, you must do a POST on the sub-resource "/gist/id/star". You may think of it as "I'm creating the sub-resource star". If you want to "unstar" this gist, you must do a DELETE on "/gist/id/star" (I'm DELETING the sub-resource star from the main resource).

In your case, I'd do something like this:

POST    v1/devices/EF-55-33-44-54-61/approval

You may think of it as "I'm creating the sub-resource approval". Maybe it would have different states, like "approval date", "user who approved it"...

DELETE v1/devices/EF-55-33-44-54-61/approval

You may think of it as "I'm removing the sub-resource "approval" and now this device doesn't have this association anymore.

Marlon
  • 888
  • 6
  • 12
  • Great answer. Question: why PUT over PATCH in this case when PATCH is intended for partial updates? – Dave New Nov 18 '14 at 05:52
  • 1
    My bad! First off, I have to edit my answer because I wrote PUT instead of POST. Now your question changes to "why POST over PATCH". :) Because you're creating the resource "approval" and not updating the attribute "status". – Marlon Nov 18 '14 at 10:55
  • 1
    Take into account that you shouldn't expose your underlying architecture through your REST API. These are two different things. You're using an OO language/architecture to develop your system, but you're exposing it through the Web and so you have to think of it in terms of resources. I don't know if I made myself clear. :P – Marlon Nov 18 '14 at 11:02
1

Would the following PATCH call make sense?

PATCH v1/devices/EF-55-33-44-54-61/approve

REST is not RPC, so you should not bind your operations to URIs, you should bind them rather to method (verb) - URI (nouns) pairs. So in your case the /approve verb is not RESTish, and your request body is not RESTish too, because it contains a command rather than a resource representation.

I would use something like these instead:

  • PUT v1/devices/EF-55-33-44-54-61/state "approved"
  • PATCH v1/devices/EF-55-33-44-54-61 {state: "approved"}

Note that real REST clients follow hyperlinks instead of building for example the URI. That's (amongst many things) what makes them loosely coupled.

inf3rno
  • 24,976
  • 11
  • 115
  • 197