1

Let's say we have a user resource which looks something like this:

{
  id: 1,
  identifier: 'U00001V002',
  name: 'bob'
}

The Restful way to update this resource would be:

PUT /users/1
{
  name: 'alice',
  ...
}

Now, what if our client does not know the resource ID (1) but it knows the identifier (which is also unique)?

How can this be achieved and still comply with REST?

zarathustra
  • 1,898
  • 3
  • 18
  • 38
  • Possible duplicate of https://stackoverflow.com/questions/10903636/rest-numeric-or-string-resource-identifiers – maio290 Sep 03 '18 at 12:00
  • @maio290 Nope. Completely different question. Your commented question aims at resource identifier formats (string vs. numeric) – zarathustra Sep 03 '18 at 12:11
  • This isn't the "RESTful way" to do it actually unless you want to replace the content of the respective user with just the new user name! `PUT` has the semantic of replacing all of the resource's content with the payload received! A "RESTful" way would already provide a client with all possible options available as respective URIs with meaningful link relation names to the client. A client therefore does not need to know how to generate the URI as it was already provided by the API's response – Roman Vottner Sep 03 '18 at 12:11
  • @RomanVottner I added "..." to the PUT payload to make it more clear. – zarathustra Sep 03 '18 at 12:24

2 Answers2

1

REST is an architectural style which, if followed stringent, allows clients to decouple themselfs from APIs/services. This allows the latter one to evolve freely without breaking clients which as a result make them more failure-robust. This properties are, however, only obtainable if you follow certain constraints like

  • Adhere to the underlying transport protocol (HTTP) to increase likelihood of cross-domain interoperability
  • HATEOAS (focus on meaningful link relation names and support of URIs provided by the API/server)
  • focus on media-types (contract of the exchanged media-type syntax and semantic)

fully. Just following it partially wont yield all the benefits it has to offer, unfortunately.

According to RFC 7231 PUT

The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload.

Loosly speaking, PUT demands that the former representation of a certain resource is being replaced with the payload received of a PUT request if it didn't violate any semantical restrictions. The server, however, is free to add or modify the request to its need, i.e. add links and other data if needed.

Hypertext as the engine of application state (HATEOAS) is one of the few constraint REST puts on the table. It demands that APIs/servers feed clients with URIs clients can use. Similar to the Web we use every day, links can be annotated with meaningful description about the content or the intent of the URI and if interested gets clicked by us humans. This concept should be translated to REST applications as well. The descriptive text accompanying the URI is called link relation name and clients should use it to determine whether to invoke the URI or not or help the client to determine when to invoke which URI. It should be as meaningful to clients as possible and may be specified either in certain media-types, common standards or domain knowledge. I.e. a pageable collection might use link relation like next, prev, first and last to give a client the possiblility to page through the different elements of a collection without the client needing to know the exact URI. It will just invoke the URI based on the link relation name. This technique helps clients to be robust in cases when the server changes its URI structure. It is rather obvious that clients that parse URIs to determine the intent will break easily if a server ever changes its URI structure, clients making use of link relations don't actually care about the concrete URI spelling used as the URI is just used to invoke the API again.

According to one of Fielding's blog post, APIs should support clients with all the information they need to proceed their task. This includes providing all necessary links a client can invoke from the current state on. This takes away the burden of clients from parsing and interpreting URIs and generating them later on to perform the request. A response from the server might contain links like the one below:

{
    "name": "bob",
    ...
    "_links": [
        "self": {
            "href": "http://.../users/1"
        },
        "identifier": {
            "href": "http://.../users/U00001V002"
        },
        "friends": [
            "tim": {
                "href": "http://.../users/2"
            },
            "sam": {
                "href": "http://.../users/3"
            },
            ...
        ],
        ...
    ]
}

What links or information to return is completly domain specific. Restricting to just JSON i.e. is a bad choice here either as JSON lacks support for links and furthermore can't describe the semantics of the actual content.

The above representation might lead to clients preassuming that certain resources have a certain type and then just create a marshaller for that specific type. Such systems might easily break if new fields are added or old ones omitted or renamed. Instead of clients using typed resources meaningful to them APIs should be designed arround media types. Fielding even states that

REST APIs should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types

Media types are the coupling part between clients and servers in a REST architecture. Instead of a client directly coupling to a respective API (and therefore needing update whenever the API changes), both, server and clients, couple to plenty of media types and negotiate about which ones they understand. Similar to browsers, applications might add support for new media types on the fly through plugins later on without even requiring a restart.

The focus on well-defined media-types further help clients and servers to avoid breaking changes later on. I.e. instead of versioning a certain API, the media type can be versioned as it defines the syntax and semantics of the headers and payload exchanged. HTML i.e. decided to stay backwards compatible to avoid breaking older clients that can't update to newer versions of the spec.

Certain media-types like application/atom+xml or application/hal+json provide certain benefits in terms of HATEOAS support. They, however, might probably be too generic for most applications. application/collection+json i.e. is also only useful for collections returned. According to Fielding media types should be generic enough to be reusable accross domains but specific enough to be usable in certain domains. I.e. to exchange user information one might use text/vcard or one of the other variants (i.e.: XML, JSON) instead of creating a new user-centric media type. A list of already defined media types can be found here

As you might hopefully see, each and every step involved in a REST architecture focuses on the decoupling of clients from APIs and to keep interoperability of different peers in that architecture as high as possible.

With that being said, as already mentioned, while an URI has to identify exactly one resource, the content of a resource might be exposed via multiple URIs. You can therefore, as proposed, just send multiple links a client can simply invoke including meaningful link-relation names as response to the client. A more proper REST way would include to switch over to media-type support in the long run and describe the syntax and semantics a client or server may expect though.

Community
  • 1
  • 1
Roman Vottner
  • 12,213
  • 5
  • 46
  • 63
1

I faced with similar problem and can suggest a few options:

  1. With some prefix for all external endpoints just use .../recourse/{external-id}. If you use different API for internal and external purposes you can use relevant IDs.
  2. On client side find resource via external-id filter and then retrieve internal-id and use it (need additional call)
  3. Use query params to identify id type .../recourse/:id?id-type={internal-id|external-id}
  4. Use .../recourse-by-external-id/:external-id
Denis.E
  • 323
  • 2
  • 10