Skip to content

Latest commit

 

History

History
133 lines (101 loc) · 5.66 KB

Performance.md

File metadata and controls

133 lines (101 loc) · 5.66 KB

Performance

{{ book.should }} Reduce Bandwidth Needs and Improve Responsiveness

APIs should support techniques for reducing bandwidth based on client needs. This holds for APIs that (might) have high payloads and/or are used in high-traffic scenarios like the public Internet and telecommunication networks. Typical examples are APIs used by mobile web app clients with (often) less bandwidth connectivity. (LambdaStack is a 'Mobile First' company, so be mindful of this point.)

Common techniques include:

  • gzip compression
  • querying field filters to retrieve a subset of resource attributes (see Support Filtering of Resource Fields below)
  • paginate lists of data items (see Pagination below)
  • ETag (and If-[None-]Match) headers to avoid refetch of unchanged resources
  • pagination for incremental access of larger (result) lists

Each of these items is described in greater detail below.

{{ book.should }} Use gzip Compression

Compress the payload of your API’s responses with gzip, unless there’s a good reason not to — for example, you are serving so many requests that the time to compress becomes a bottleneck. This helps to transport data faster over the network (fewer bytes) and makes frontends respond faster.

Though gzip compression might be the default choice for server payload, the server should also support payload without compression and its client control via Accept-Encoding request header -- see also RFC 7231 Section 5.3.4. The server should indicate used gzip compression via the Content-Encoding header.

{{ book.should }} Support Filtering of Resource Fields

Depending on your use case and payload size, you can significantly reduce network bandwidth need by supporting filtering of returned entity fields. Here, the client can determine the subset of fields he wants to receive via the fields query parameter — example see Google AppEngine API's partial response:

Unfiltered

GET http://api.example.org/resources/123 HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "cddd5e44-dae0-11e5-8c01-63ed66ab2da5",
  "name": "John Doe",
  "address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
  "birthday": "1984-09-13",
  "partner": {
    "id": "1fb43648-dae1-11e5-aa01-1fbc3abb1cd0",
    "name": "Jane Doe",
    "address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
    "birthday": "1988-04-07"
  }
}

Filtered

GET http://api.example.org/resources/123?fields=(name,partner(name)) HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json

{
  "name": "John Doe",
  "partner": {
    "name": "Jane Doe"
  }
}

The approach we recommend for field filtering is a LambdaStack Github project, json-fields. It defines a formal grammar for the ANTLR parser generator and provides a ready-to use library for Java / Jackson based projects (Maven link). Teams that use other JSON serializers are encouraged to contribute to the open source project and create their own parser / framework based on this grammar.

Other approaches we have considered are JSONPath or GraphQL. While they have advantages, neither of them can easily be plugged into an existing serialization process, so they require an additional, manual serialization process, whereas the above solution addresses our main filter use cases and can easily be introduced with a minimum of effort.

Hint: OpenAPI doesn't allow you to formally specify whether depending on a given parameter will return different parts of the specified result schema. Explain this in English in the parameter description.

{{ book.could }} Support the ETag Header

If a resource changes, the contents of the ETag header must also change. Combined with the If-Match and If-None-Match headers, the ETag header allows caching of entities.

Its possible contents:

  • the entity’s version number
  • hash of the response body
  • hash of the entity’s last modified field

Also see Allow Embedding of Complex Sub-Resources below as contribution to performance optimization to avoid multiple requests for sub-resources.

{{ book.should }} Allow Optional Embedding of Sub-Resources

Embedding related resources (also know as Resource expansion) is a great way to reduce the number of requests. In cases where clients know upfront that they need some related resources they can instruct the server to prefetch that data eagerly. Whether this is optimized on the server, e.g. a database join, or done in a generic way, e.g. an HTTP proxy that transparently embeds resources, is up to the implementation.

See Conventional Query Strings for naming. Please use json-fields library, already mentioned above for filtering, when it comes to an embedding query syntax.

Embedding a sub-resource can possibly look like this where an order resource has its order items as sub-resource (/order/{orderId}/items):

GET /order/123?embed=(items) HTTP/1.1

{
  "id": "123",
  "_embedded": {
    "items": [
      {
        "position": 1,
        "sku": "1234-ABCD-7890",
        "price": {
          "amount": 71.99,
          "currency": "EUR"
        }
      }
    ]
  }
}