Question

I'm having a design issue in my REST api, where I have a resource Device, which happens to represent an IoT device, can be partially updated using PATCH, but some of the things that get updated will begin a long running job.

For instance,

Here is class Device:

public class Device
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Ip { get; set; }
    public string Foo {get; set; }
}

I can do a PATCH request like so:

PATCH /api/devices/1234
{
    name: "abc123",
    Foo: "hello"
}

which will update both the Name & Bar properties of the device.

However, if I want to update the Ip field using partial update like so:

PATCH /api/devices/1234
{
    name: "abc123",
    ip: "1.2.3.4"
}

The issue is updating the ip on Device triggers an API call to the physical IoT device to set the ip, which causes the device to reboot.

Usually for long running jobs, I would do something similar to what is described here, and return 202 accepted with the URL of the resource so you can track the operation.

Is it a good idea to do this in conjunction with partial update? Say the ip field is specified in my patch, does it make sense to do something like this:

PATCH /api/devices/1234
{
    name: "abc123",
    ip: "1.2.3.4"
}
Response:
{
    location: "/api/tasks/123"
}

This way the request can be tracked, so that the client can know when the device has successfully rebooted... But it feels awkward that the API will only sometimes respond like this. Only if you specify a property that will trigger a long-running job... Not sure what the best design choice is here.

Any advice is welcome.

Was it helpful?

Solution

RESTful Casuistry has a pretty good discussion of restart; if you aren't already familiar with it, you should have a look at it.

Usually for long running jobs, I would do something similar to what is described here, and return 202 accepted with the URL of the resource so you can track the operation.

Is it a good idea to do this in conjunction with partial update?

Yes.

But it feels awkward that the API will only sometimes respond like this.

Well, there's nothing necessarily wrong with always reporting that the request has been accepted.

I've come to find that the easiest way to work through this is to think about the messages first, and then worry about the meta data afterwards.

In HTTP, the response to an unsafe operation is generally a message describing the result of the action. If you imagine a website submitting a form, you might get a page back saying "the operation succeeded, follow this link to see the result", or you might get a page back saying "the operation was accepted, follow this link to get the latest status".

The same idea expressed another way: you are navigating an integration protocol; sometimes the application transitions to the finished state, other times it transitions to an intermediate pending state.

The intermediate state really isn't inherently different from a failed state, where you get a response back describing the fact that the request you submitted was rejected, and here's what you can do about it.

The HTTP status code, and the response headers, are not the response; they are meta data lifted from the response. What we're really doing there is taking semantics from the response (which targets the consumer), and lifting some of them into the metadata so that the generic components participating in the protocol have a coarse understanding of what's going on, and can act appropriately.

But it feels awkward that the API will only sometimes respond like this.

Think about the web sites we have today. Sometimes you click on link, and it takes you to the data you expect; but sometimes instead you are directed to a log in screen because your previous license has expired.

In other words, we do this sort of thing all the time; there are multiple states that the application might transition to, the server picks one, and the client has to figure out how to make progress.

The REST answer to the latter is hypermedia, which is to say the client needs to understand the semantics of the links that could be returned by the server, and the server chooses to respond with the appropriate links, including the semantic annotations that distinguish them.

In your example, most of your use cases have data ready immediately, so the "result of the operation" response will include only the link that informs the client of how to view the result

<a ref="http://example.org/viewResult" href="...">

For the restart case, which is going to take some time, the "result of the operation response will include only the link that informs the client of how to check for progress

<a rel="http://example.org/checkProgress" href="...">

Another way of thinking about this: the presence of the link tells the client that a resource is available, the link relation tells the client what the resource is, the protocol tells the client what actions they can take with that kind of resource (which http methods are supported, what media-types are available, and so on).

Atom Publishing is a really good example of a protocol defined this way.

OTHER TIPS

Create a GUID immediately and return it in the response after asynchronously initiating the job:

{
    "UpdateId": myguid,
    "Result": "Update Started"
}

You then have an additional method:

DeviceUpdateStatus

This takes the guid as an argument and can be queries so that the client knows whether the update has completed or not.

Licensed under: CC-BY-SA with attribution
scroll top