segunda-feira, 12 de setembro de 2016

REST anti-patterns

In the past few years I've been working and studying RESTful APIs, and I have seen some common mistakes in different projects and on online forums, then I decided to write this post based on some experiences and on stuff I've read on the internet.
Here are some anti-patterns, their explanation and examples.

URI not very RESTful


Your URI does not reflect the action that's happening under an existing resource.

RESTful APIs are about resources, when we're building our URIs, we need to tell a story about that resource, looking at the URI the consumer must understand all about the given resource, where it came from, which is its identifier, which options it has.

Let's say we have a resource called account and we need to close this account, what is the best way to represent this action?

Below I've divided some examples in wrong and correct. Which one makes more sense to you?

Wrong
  • POST /accounts/close
  • POST /closeAccount
Correct
  • POST /accounts/4402278/close
The wrong options doesn't give this visibility when looking at the URI. Probably we need to send some query parameters or a body, but it's not clear which is the name of the query parameter, neither the format of the body if needed

The correct option shows that the "account" 4402278 can be closed, we can deduce it just looking at the URI.

Using wrong HTTP methods


The HTTP methods must be used to give the intent of the action that is happening. If you are returning information, you must use GETS for example.

Below is a list of actions and its respective methods for the most common HTTP methods.

GET - Retrieve records
POST - Create records
PUT - Update whole records
PATCH - Updates pieces of records
DELETE - Delete records

Having said that, mistakes such as below are often seen:
Wrong
  • POST /accounts/4402278/delete
Correct
  • DELETE /accounts/4402278

The wrong example is doing a POST on accounts for a given id asking a delete option explicitly on the URI.

It's somehow clear, however you are doing it wrong because HTTP has a explicit method for deleting resources, which is DELETE.
There are many other cases which could make this post even bigger, but here is just the challenging.

Hurting Idempotency


No matter how many times you call GET on the same resource, the response should always be the same and no change in application state should occur.
  • Idempotent methods: GET, PUT, OPTIONS
  • Non Idempotent methods: POST

What about the method DELETE? If you DELETE /accounts/4402278 twice
  • The accounts will not be deleted manhttps://http.cat/405y times ... thinking this way it is idempotent
  • The second time the resource will not be found and should return a 404 Not found, this way it's not idempotent anymore

https://www.youtube.com/watch?v=6dVNdFwqeKs

Ignoring status codes


If your API only returns 200 (OK) or 500 (Internal Server Error), you are hurting the response codes.

The status codes were created to give the consumer an overall status of the final state of the request.

It means we need to be carefull when choosing status codes to represend this state of the requisitions, it need to reflect exaclty what happened and the end result.
Wrong:
  • GET /accounts/123456 (and there is no matching record) response: HTTP status 200 (ok) with a body saying it's not found
Correct:
  • GET /accounts/123456 (and there is no matching record) response: HTTP status 404 (not found)
If you are getting a resource as the example below, and there is no matching record, it should return 404, because it was not found. If you are returning a 200(ok) with a message in the body saying "Not found", you are doing it wrong, the status code says "ok" but the message says "not found", it's completely redundant.

Status codes will also help to give more clarity on the responses of your API. In many cases consumers only have to parse the status code to know exactly what happened, much simpler than parse responses with big strings.

HTTP status codes


Here are some of the most used, in my humble opinion :)

2xx - Success4xx / 5xx - Error3xx - Redirection
200 OK400 Bad Request301 Moved
201 Created401 Unauthorized302 Found
203 Partial Information402 Payment Required304 Not Modified
204 No response403 Forbidden
404 Not Found
500 Internal Server Error
503 Service Unavailable

Ignoring caching


It is easy to ignore the caching by including a header "Cache-control: no-cache" in responses of your API calls.

HTTP defines a powerful caching mechanism that include ETag, If-Modified-Since header, and 304 Not Modified response code.

They allow your clients and servers to negotiate always a fresh copy of the resource and through caching or proxy servers increase you application's scalability and performance.

Ignoring hypermedia


If your API calls send representations that do not contain any links, you are most likely breaking the REST principle called HATEOAS.

Hypermedia is the concept of linking resources together allowing applications to move from one state to another by following links.

If you ignore hypermedia, it is likely that URIs must be created at the client-side by using some hard-coded knowledge.

More on HATEOAS


Client interacts with a application through hypermedia provided dynamically the API

Current state of the application is defined by your data and the links on your payloads.

Client must have a generic understanding of hypermedia.

Allows the server functionality to evolve independently.

Interaction is driven by hypermedia, rather than out-of-band information.

Example


In the example below we have a resource called "account" which has 100.00 on it.

{
    "accounts": [
        {
            "accountNumber": "4502278",
            "balance": 100.00,
            "links": [
              {"rel": "deposit", href: "/account/4502278/deposit"},
              {"rel": "withdraw", href: "/account/4502278/withdraw"},
              {"rel": "transfer", href: "/account/4502278/transfer"},
              {"rel": "close", href: "/account/4502278/close"}
            ]
        }
    ]
}


Let's say now the owner of the account is on "the red" at this moment. the API should block some actions and show the payload such as:

{
    "accounts": [
        {
            "accountNumber": "4502278",
            "balance": -60.55,
            "links": [
                {"rel": "deposit", href: "/account/4502278/deposit"}
            ]
        }
    ]
}


Ignoring MIME types


If resources returned by API calls only have a single representation, you are probably only able to serve a limited number of clients that can understand the representation.

If you want to increase a number of clients that can potentially use your API, you should use HTTP's content negotiation.

It allows you to specify standard media types for representations of your resource such as XML, JSON or YAML

Conclusion


When building your APIs ...
  • Be coherent
  • Require headers
  • Use Standards (JSON-API)
  • Build well designed URIs
  • Return coherent status codes
  • Care about idempotency
  • Use correct HTTP methods

I really hope I helped you identifying some common mistakes and I also hope the tips given here will help you when designing your APIs.

Tweet me if you wanna discuss more about it.

10 comentários:

  1. Hurting Idempotency - what does this line mean:

    The accounts will not be deleted manhttps://http.cat/405y times ... thinking this way it is idempotent

    ResponderExcluir
    Respostas
    1. I also would like to know. For my understanding only GET is idempotent.

      PUT and DELETE are modifying the resource in a way or another.

      Excluir
    2. idempotency is about the end state, the resource will no be deleted twice.
      in the case of PUT, if you are updating status to Active, it'll be active even if you run the PUT 1000 times

      Excluir
    3. cool, yeah, I think you can interpret it as you want. agree with your point of view!

      Excluir
    4. Thanks for the answer, yes from that point of view what you say is right :)

      Excluir
    5. in Ruby on Rails active record PUT updates updatedAt timestamp, which means that it updates record on each request, so it's not idempotent.

      Excluir
    6. PUT is considered idempotent. Why? Because no matter how many times you call a `PUT` endpoint, you will always get the same result.

      Excluir
  2. REST has nothing to do with meaningful(meaningless) URIs. If you're using HATEOAS, clients aren't supposed to know about URIs anyway

    ResponderExcluir
    Respostas
    1. Maybe, but HATEOAS helps usability of you API. If you have the same API in different domains (or change domain), the client only needs to point the API to the specific domain once, since it will (or should) use "rel" instead of "href", thus avoiding the work to update the URIs of specific actions.

      Excluir
  3. I have a confusion about
    POST /accounts/:id/close
    Maybe we can deduce from URI easily,but is it security?
    Somebody will intercept network packet and find out user's account more easily.

    ResponderExcluir