简体   繁体   中英

REST API design: different granularity for receiving and updating resources

I'm in the process of creating a REST API. Among others, there is a resource-type called company which has quite a lot of attributes/fields.

The two common use cases when dealing with company resources are:

  • Load a whole company and all its attributes with one single request
  • Update a (relatively small) set of attributes of a company, but never all attributes at the same time

I came up with two different approaches regarding the design of the API and need to pick one of them (maybe there are even better approaches, so feel free to comment):


1. Using subresources for fine-grained updates

Since the attributes of a company can be grouped into categories (eg street, city and state represent an address... phone, mail and fax represent contact information and so on...), one approach could be to use the following routes:

/company/id : can be used to fetch a whole company using GET

/company/id/address : can be used to update address information (street, city...) using PUT

/company/id/contact : can be used to update contact information (phone, mail...) using PUT

And so on.

But: Using GET on subresources like /company/id/address would never happen. Likewise, updating /company/id itself would also never happen (see use cases above). I'm not sure if this approach follows the idea of REST since I'm loading and manipulating the same data using different URLs.

2. Using HTTP PATCH for fine-grained updates

In this approach, there are no extra routes for partial updates. Instead, there is only one endpoint:

/company/id : can be used to fetch a whole company using GET and, at the same time, to update a subset of the resource (address, contact info etc.) using PATCH .


From a technical point of view, I'm quite sure that both approaches would work fine. However, I don't want to use REST in a way that it isn't supposed to be used. Which approach do you prefer?

Do you really nead each and every field contained in the GET response all the time? If not, than its more than just fine to create own resources for addresses and contacts. Maybe you will later find a further use-case where you might reuse these resources.

Moreover, you can embed other resources as well in resources. JSON HAL ( ) fe explicitely provides an _embedded property where you can embed the current state of fe sub-resources. A simplified HAL-like JSON representation of an imaginary company resource with embedded resources could look like this:

{
    "name":"Test Company",
    "businessType":"PLC",
    "foundingYear": 2010,
    "founders": [
        {
            "name": "Tim Test",
            "_links": {
                "self": {
                    "href": "http://example.org/persons/1234"
                }
            }
        }
    ],
    ...
    "_embedded": {
        "address": {
            "street": "Main Street 1",
            "city": "Big Town",
            "zipCode": "12345",
            "country": "Neverland"
            "_links": {
                "self": {
                    "href": "http://example.org/companies/someCompanyId/address/1"
                },
                "googleMaps": {
                    "href": "http://maps.google.com/?ll=39.774769,-74.86084"
                }
            }
        },
        "contacts":  {
            "CEO": {
                "name": "Maria Sample",
                ...
                "_links": {
                    "self": {
                        "href": "http://example.org/persons/1235"
                    }
                }
            },
            ...
        }
    }
}

Updating embedded resources therefore is straigtforward by sending a PUT request to the enclosed URI of the particluar resource. As GET requests my be cached, you might need to provide finer grained caching settings (fe with conditional GET requests aka If-Modified-Since or ETAG header fields) to retrieve the actual state after an update. These headers should consider the whole resource (incl. embedded once) in order to return the updated state.

Concerning PUT vs. PATCH for "partial updates":

While the semantics of PUT are rather clear, PATCH is often confused with a partial update by just sending the new state for some properties to the service. This article however describes what PATCH really should do.

In short, for a PATCH request a client is responsible for comparing the current state of a resource and calculating the necessary steps to transform the current resource to the desired state. After calculating the steps, the request will have to contain instructions the server has to understand to execute these instructions and therefore produces the updated version. A PATCH request is furthermore atomic - either all instructions succeed or none. This adds some transaction requirements to this request.

In this particular case I'd use PATCH instead of subresources approach. First of all this isn't a real subresources. It's just a fake abstraction introduced to eliminate the problem of updating the whole big entity (resource). Whereas PATCH is a REST compatible, well established and common approach.

And (IMO ultima ratio ), imagine that you need to extend company somehow (by adding magazine, venue, CTO, whatever). Will you be adding a new endpoint to enable client to update this newly-added part of a resource? How it finishes? With multiple endpoint that no one understands. With PATCH your API is ready for new elements of a company.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM