January 1, 0001 by rdagumampan

The way we do REST API

The document serves as the reccomended practices when designing our REST-based API for internal service-to-service integration. The guide is created from existing experiences and various sources available online and deliberately simplified to accomodate greater flexibility and autonomy for developers to successfuly delivery of value with our API.

We accept the fact that we will not be able to please everyone and we cannot get everything right the first time. This guide is living document and must be continuously updated as we get better and wiser.

Why build an API

  • api is an abstraction from the complexity of existing internal subsystems
  • api is a facade to backend systems to delivery capability
  • api is a contract to consumers of data and services

“As flexibility of system increases, its usability decreases.” - Flexibility-usability tradeoff, Universal Principles of Design

Why RESTful API

  • simple, uses native HTTP request and verbs
  • standard and widely supported by frameworks and tools

Why invest in good API design

  • because api is one of our assets and can be potential liabilities
  • because we rarely get second chance to have sufficient design window after it reached production
  • because it makes developers happy

API Design Principles

  • self-describing
  • stateless
  • composable
  • idempotent
  • consistent

Supported Methods

Idempotent requests means the request can expect the same results if the same request parameters are submitted multiple times. A POST for inserting new object is not idempotent because we expect it to create the new resource everytime.

Method Description Is Idempotent
GET Return the current value of an object Y
PUT Replace an object, or create a named object, when applicable Y
DELETE Delete an object Y
POST Create a new object based on the data provided, or submit a command N

Base URI

GET http://api-{serviceRoot}.{serviceHost}:{servicePort}/{version}/
GET http://api-ots.de-prod.dk:5000/v1/
GET http://api-ocs.de-prod.dk:5000/v1/
GET http://api-inc.de-prod.dk:5000/v1/
GET http://api-wo.de-prod.dk:5000/v1/

Naming

  • use plural nouns, no verbs
  • use concrete names, small caps, no abbreviations
  • use full words, no underscore, no hyphen, no special characters
GET /outages 
GET /classifications
GET /incidents/
GET /workorders/

Request headers

Header name Description Required
x-api-request-id GUID to identify the request Y
x-api-request-time-utc UTC time when request was made from client Y
x-api-version Api version to be used Y
x-api-correlation-csvid Command separated GUID of other requests related to the current request
x-api-request-toc-secs Expected time to complete the operation. Unit in milliseconds.

Response headers

Header name Description Required
x-api-request-id GUID to identify the request Y
x-api-response-id GUID to identify the response Y
x-api-request-time-utc UTC time when request was made from client Y
x-api-version Api version to be used Y
x-api-update-time-utc UTC time when POST/PUT has been completed in server

Response status Codes

Limt our response codes to the following.

HTTP status code Response condition
200 Ok Successful call
400 Bad Requests Validation errors
500 Internal Server Error Internal exception occured
404 Not Found Resource does not exist
201 Created Resource is created
401 Unauthorized Authentication failed for request
403 Forbidden Authentication is OK but not authorized for access to resource

Response to good request

PUT api-wo.de-prod.dk/v1/avn01/workorders HTTP/1.1 200 OK
Content-Type: application/json

{
    "workOrderNo":"WO-001",
    "siteId": "AVN01",
    "wtgId": "AVN01A01"
    ...
    ...
}

Response to bad request

PUT api-wo.de-prod.dk/v1/avn01/workorders HTTP/1.1 400 Bad Request
Content-Type: application/json

errors [
{
    "status":"400",
    "code":"REQ01",
    "title":"Missing asset id.",
    "detail":"Missing value for required property in request payload.",
    "href":"http://api-wo.de-prod.dk/v1/help?e=REQ01"
}]

Response after an internal server error

GET api-wo.de-prod.dk/v1/avn01/workorders/wo-001 HTTP/1.1 500 Internal Server Error
Content-Type: application/json

errors [
{
    "status":"500",
    "code":"SAP1001",
    "title":"Work order service not available",
    "detail":"Request timeout while attemting to establish handskare to SAP PI/PO.",
    "href":"http://api-wo.de-prod.dk/v1/help?e=SAP1001"
}]

Paging and Partial Results

Use default limit=10, offset=0.

GET api-wo.de-prod.dk/v1/workorders
GET api-wo.de-prod.dk/v1/workorders?limit=25&offset=50

Associations

Limit association to two levels collections/item/collection.

GET api-wo.de-prod.dk/v1/avn01/workorders/
GET api-wo.de-prod.dk/v1/avn01/workorders/wo-1234/tasks

Statistics

GET api-wo.de-prod.dk/v1/workorders/stats HTTP/1.1 200 OK
Content-Type: application/json

{
  recordCount: 75
}
GET api-wo.de-prod.dk/v1/workorders/search?q=

Health

GET api.orsted.dk/v1/health HTTP/1.1 200 OK
Content-Type: application/json

{
    "status":"Ok",
    "timestampUtc": "2018-10-25T10:30:00.000Z"
}

Help

GET api-wo.de-prod.dk/v1/help
GET api-wo.de-prod.dk/v1/help?e={error_code}

Short-lived operations

POST api-wo.de-prod.dk/v1/avn01/production/calculate 200 OK

Long running operations

POST api-wo.de-prod.dk/v1/avn01/curtailment/calculate HTTP/1.1 200 OK
Content-Type: application/json

{
    "operationId":"1234"
    "status":"Running",
    "startTimeUtc": "2018-10-25T10:30:00.000Z",
}
GET api-wo.de-prod.dk/v1/avn01/curtailment/operations/1234 HTTP/1.1 200 OK
Content-Type: application/json

{
    "status":"Running",
    "startTimeUtc": "2018-10-25T10:30:00.000Z",
}

Protocol & Format

  • content/type: http/json and https/json
  • datetime/format: Iso8601Literal yyyy-mm-ddTHH:MM:SS.MMMMMZ

Payload Validation

Data Transfer Objects (DTO) must have built-in contraints to pre-validate payload. This serves as first defense against bad requests. Failed validations must return 500 Bad Request response code.

  • string length
  • numeric range values
  • date range values
  • date formats
  • type matches
  • required value

Authentication

As the intended purpsose of our API is for service-to-service integration, API owner can issue client API keys. Clients submits requests with Basic Auth HTTP header over SSL channel. The Basic Auth header is a Base64 Hash text and thus needs to be encrypted in transport.

GET https://api-wo.de-prod.dk/v1/avn01/workorders/wo-001
Content-Type: application/json
Authorization: Basic ZWx1c3VhcmlvOnlsYWNsYXZl

Timeout

Our API implementation must set execution threshold. When breached, return a 500 Interal Server Error with sufficient details of the fault.

apiMethod.Run(()=> {
    //do more here
}, timeOut.10.Seconds);

Transient Fault Handling

Backend systems may occasionally fail and our API must have built in resilience to such faults. When breached, return a 500 Interal Server Error with sufficient details of the fault.

var retryPolicy = RetryPolicyFactory
    .After(1.Seconds)
    .After(3.Seconds)
    .After(5.Seconds)
    .Create();

retryPolicy.Execute(()=> {
    //do more here
}, new ApplicationException("Request failed."));

Non-resource endpoints

GET api-wo.de-prod.dk/v1/workorders/calculate

Versioning

Crea a new version for: - breaking changes to clients - deprecated services affecting existing clients - changes to contract

GET api.orsted.dk/v1/workorders

Do not create new version for:

  • new resource
  • extended response dto
  • changed technology
  • changed backend services

Documenting

  • embedded swagger
  • api-wo.de-prod.dk/v1/help

Testing

  • create fake backend sources
  • run specflow tests to simulate api calls base on scenario
  • the specflow serves as your integration tests
  • docker compose your test suite

Unsupported Features

  • authorization, all requests are authorized
  • sorting, clients is responsible for sorting
  • partial attributes
  • caching
  • wild card search
  • rate limiting

References:

Feedback

for comments, change requests, and further discussions just ping me: Rodel Dagumampan / RDDAG v0.120181007

© 2017 | About | Contact | Follow me on Twitter | Powerered by Hucore & Hugo