Ein Microservice alleine macht noch keinen Sommer. Interessant wird es erst, wenn viele scheinbar unabhängige Services ein großes Ganzes bilden. Als Kommunikationsmuster zwischen den einzelnen Services wird dabei nicht selten auf REST zurückgegriffen. So weit, so gut. Aber wie sieht eine wirklich gute, und vor allem zukunftssichere REST-Schnittstelle aus? Welches Austauschformat sollte man wählen? XML, JSON, Binary oder am besten gleich alle drei? Wie geht man mit dem Thema Versionierung um? Und wie sichert man das API gegen unbefugte Benutzung ab? Welche Rolle spielen die Response-Header? Was ist mit dem Thema „Error Handling“? Und wie nutzt man möglichst effektiv die verschiedenen HTTP-Statuscodes? Macht es Sinn, für unterschiedliche Channels unterschiedliche Schnittstellen anzubieten? Und was ist noch einmal dieses HATEOAS? All diese Fragen – und viele weitere – wollen wir uns am Beispiel eines eigenen API-Designs anschauen. Ausgehend von einer einfachen REST-Schnittstelle, werden wir Schritt für Schritt neue Anforderungen einführen und dafür passende praxisnahe Lösungen entwickeln.
6. #WISSENTEILEN
Warum REST?
Keep it stupid simple a.k.a. KISS
• REpresentational State Transfer
• Identifikation von Ressourcen durch URL
• Manipulation von Ressourcen durch Representation
• Selbstbeschreibende Messages
• Hypermedia
/customers/123
JSON/XML
GET, POST, PUT, DELETE
Media Types, CachabilityReferences
{?}
12. #WISSENTEILEN
„Order 123 for
‚Larissa‘.“
„And here‘s the
receipt.“
„Coffee, latte,
large, to-go,
semi, double
shot, please.“
„Here‘s 5$.“
„What‘s the
status of ‚123‘?“„Still prepering.“
„Now its ready.“
REST by Example
13. #WISSENTEILEN
// order coffee
POST /orders
{ ... initial order data ... }
// check order status
GET /orders/1234
// change order
PUT /orders/1234
{ ... changes ... }
// cancel order
DELETE /orders/1234
RESTBucks
„crud“
given id?
filter, query, sorting?
PUT? Or PATCH?
Security?
Versionioning?
Error Handling?
Content Type?
15. #WISSENTEILEN
• „We only need two URLs“
• „Verbs are bad, nouns are good“
• „Plurals are even better“
• „The web is your friend“
• „There is always a root (for associations)“
• „There is always a parameter (for complex variations)“
• „There is always a method (for an operation)“
Golden Rules of REST
16. #WISSENTEILEN
// Retrieve single order with id 1234
GET /orders/1234
// Retrieve all ingredients of order 1234
GET /orders/1234/ingredients
// Retrieve ingriedient 789 (milk) of order 1234?
GET /orders/1234/ingredients/789
// Or even better: What kind of milk is in order 1234?
GET /orders/1234/ingredients/milk
REST
„root“
17. #WISSENTEILEN
// Path parameter for identifier
GET /orders/1234
// Query parameter for queries
GET /orders/?status=ready
// Header parameter for platform
GET /orders/1234
Accept-Language: de-DE
REST
„param“
18. #WISSENTEILEN
// „Create order“, isn‘t it?
POST /orders
// Is this allowed? Guess not. Or ...?
POST /orders/1234
// „Change order“. I‘am sure!
PUT /orders/1234
// „Change order“, too? Isn‘t it?
PATCH /orders/1234
REST
„method“
19. #WISSENTEILEN
POST vs. PUT vs. PATCH
POST
erzeugt Child Resource an Server-definierter URI
PUT
erzeugt/ändert Child Resource an Client-definierter URI
PATCH
ändert Teile einer Child Resource an Client-definierter URI
20. #WISSENTEILEN
// Creates order. Returns ID 1234
POST /orders
{ ... payload for 1234 ... }
// Hmmm, updates order 1234? Creates new order?
POST /orders
{ ... payload for 1234 ... }
REST
„method“
21. #WISSENTEILEN
// Changes order with ID 1234
PUT /orders/1234
{ ... changes for 1234 ... }
// Hmmm, will be irgnored?
PUT /orders/1234
{ ... changes for 1234, e.g. set amount of milk ... }
// Or additional changes?
PUT /orders/1234
{ ... changes for 1234, e.g. add more milk ... }
REST
„method“
22. #WISSENTEILEN
// Changes order with ID 1234
PUT /orders/1234
{ ... changes for 1234 ... }
// Same as PUT? Mayby not!
PATCH /orders/1234
{ ... changes for 1234 ... }
// Same as PUT? Is this what i expect?
GET /orders/1234?milk=soja or
GET /orders/1234/addSojaMilk
REST
„method“
23. #WISSENTEILEN
SAFE / IDEMPOTENT Methods
SAFE*
keine Änderung an der Ressource
IDEMPOTENT**
einmalige Änderung an der Ressource-Representation
mehrfache Wiederholung führt immer zum selben Ergebnis
* GET, HEAD, OPTIONS
** GET, HEAD, OPTIONS, PUT, DELETE
25. #WISSENTEILEN
Filter, Sorting, Pagination,
Was ist eigentlich mit ...
• komplex(er)en Anfragen?
• eingeschränkten Rückgabeobjekten?
• (alternativen) Sortierungen?
• alternativen Rückgabeformat?
Und wie sieht es mit „allgemeiner“ Suche aus?
26. #WISSENTEILEN
// FILTERING:
// List of paid orders (2015-12-20)
// Common Style
GET /orders?date=20151220&status=payed HTTP 1.1
REST
„filter“
27. #WISSENTEILEN
// FILTERING:
// Details of order 3 (product, date, ...)
// Facebook Style
GET /orders/3?fields=product,date,status HTTP/1.1
GET /orders/3?fields=item.product,date,status HTTP/1.1
// LinkedIn Style
GET /orders/3:(product, date, status) HTTP/1.1
REST
„filter“
28. #WISSENTEILEN
// FILTERING:
// Details of order 3
// without date, status
GET /orders/3?exclude=date,status HTTP/1.1
// predefined payload (compact = product, date, status)
GET /orders/3?style=compact HTTP/1.1
REST
„filter“
What is compact?
29. #WISSENTEILEN
// FILTERING:
// Details of order 3
// using PREFER HEADER for response payload definition
GET /orders/3 HTTP/1.1
Content-Type: application/json
Prefer: return=compact-format
HTTP 1.1 200 OK
Content-Type: application/json; charset=utf-8
Preference-Applied: return=compact-format
REST
„filter“
30. #WISSENTEILEN
// SORTING:
// orders sorted (date ↓ /item ↑)
// SQL alike style
GET /orders?sort=date+DESC,item+ASC HTTP/1.1
// Sort and asc/desc combination, ascending as default
GET /orders?sort=date,item&desc=date HTTP/1.1
// use prefix „-“ for descending, ascending as default
GET /orders?sort=-date,item HTTP/1.1
REST
„sorting“
31. #WISSENTEILEN
// FILTERING & SORTING:
// orders of „today“ sorted by ...
// long version
GET /orders?status=open
&date_from=20170510&date_to=20170510
&fields=product,status,time&sort=-time,product
// short and readable version
GET /orders/open_orders_of_today
GET /open_orders_of_today
REST
„filter&sort“
32. #WISSENTEILEN
// Pagination:
// return page 4 of orders
// Is page a query parameter?
GET /orders?page=4 HTTP/1.1
// Or is page a „virtual“ resource?
GET /orders/pages/4 HTTP/1.1
REST
„pagination“
„4“?
PREV? NEXT?
FIRST? LAST?
33. #WISSENTEILEN
// Pagination:
// return page 4 of orders
// get “page 4“ and info about PREV/NEXT
GET /orders?offset=20&limit=5 HTTP/1.1
// Response with success code and link header for
// navigation purpose
HTTP/1.1. 200 OK
Link: <.../orders?offset=0&limit=5>; rel=„first“
<.../orders?offset=5&limit=5>; rel=„prev“,
<.../orders?offset=15&limit=5>; rel=„next“,
<.../orders?offset=40&limit=2>; rel=„last“
REST
„pagination“
34. #WISSENTEILEN
// Pagination:
// return page 4 of orders
// get “page 4“ and info about PREV/NEXT
GET /orders?page=4&limit=5 HTTP/1.1
// Response with success code and link header for
// navigation purpose
HTTP/1.1. 200 OK
Link: <.../orders?page=1&limit=5>; rel=„first“
<.../orders?page=3&limit=5>; rel=„prev“,
<.../orders?page=5&limit=5>; rel=„next“,
<.../orders?page=8&limit=2>; rel=„last“
REST
„pagination“
35. #WISSENTEILEN
// FULL TEXT SEARCH:
// Fulltext search for coffee
// Global style via virtual resource
GET /searches?q=coffee HTTP/1.1
// Scoped style
GET /orders/searches?q=coffee HTTP/1.1
GET /orders?q=coffee HTTP/1.1
REST
„search“
36. #WISSENTEILEN
// ADVANCED SEARCH:
// Coffee WITH milk for 2€
// Query for ...
GET /orders?type=coffee&ingredient=milk&price=2
REST
„search“
BTW: AND or OR or
AND/OR?
37. #WISSENTEILEN
// ADVANCED SEARCH:
// Coffee WITH milk for LESS THAN 2€
// Query for ...
GET /orders?query=type=coffee+ingredient=milk+price<=2
REST
„search“
Build your own
„Query Language“?
39. #WISSENTEILEN
// ADVANCED SEARCH:
// Coffee WITH milk for LESS THAN 2€
// RQL query ... (must possibly be encoded!)
GET /orders?query=
and(eq(type,coffee),
or(eq(ingredients,MILK),lt(price,2))
// RQL alternative query – FIQL (URI friendly) - ...
GET /orders?query=
type==coffee;(ingredients==MILK,price=lt=2)
REST
„search“
40. #WISSENTEILEN
// SUPER ADVANCED SEARCH:
// All stores with
// - employees older than 20 years
// - who have soled more than 1000 coffee/day
// - even if it was damnd hot outside (> 30 degree)
#WTH
REST
„search“
41. #WISSENTEILEN
Filter, Sorting, Pagination
Super Advanced Queries
• teure Client-Server Roundtrips (n+1 oder mehr)
• virtuelle Ressourcen (statisch? dynamisch?)
• GraphQL
als Alternative unbedingt anschauen!
(* https://github.com/persvr/rql)
43. #WISSENTEILEN
// SUPER ADVANCED SEARCH:
// All stores and their sales
// - employees of age 20
// - have sold at least 1000 coffee/day
// - even if it was damnd hot (> 30 degree)
{
stores {
name
sales(unit: EURO)
employees(age:20, soldCoffee:1000) {
...
}
}
}
REST
„graphQL“
45. #WISSENTEILEN
Pro Tipp: Use them!
• 1xx: Hold on ...
• 2xx: Here you go!
• 3xx: Go away!
• 4xx: You f#!?ed up!
• 5xx: I f#!?ed up!
(http://restlet.com/http-status-codes-map)
Facebook
„Always 200“
Anti-Pattern
HTTP Status Codes
(http://www.restpatterns.org/HTTP_Status_Codes)
47. #WISSENTEILEN
// HTTP Status Codes:
// signal that new order was created
// „Create new Resource“ of type order
POST /orders HTTP/1.1
[various other headers]
// Response with location header pointing to resource
HTTP/1.1. 201 Created
Location: http//restbucks.com/api/orders/1234
REST
„Codes“
48. #WISSENTEILEN
// HTTP Status Codes:
// signal that some work is going on
// Trigger some async workload
POST /orders/... HTTP/1.1
[various other headers]
// Response without payload, cause it‘s not yet calculated
HTTP/1.1. 202 Accepted
REST
„Codes“
49. #WISSENTEILEN
// HTTP Status Codes:
// signal that payload is empty
// DELETE order with id 1234
DELETE /orders/1234 HTTP/1.1
[various other headers]
// Order successfully deleted. No content by purpose
HTTP/1.1. 204 No content
HTTP/1.1. 205 Reset content
REST
„Codes“
50. #WISSENTEILEN
// HTTP Status Codes:
// signal that payload is empty
// Retrieve all open orders
GET /orders?status=open HTTP/1.1
[various other headers]
// There is no open order left.
HTTP/1.1. 204 No content
REST
„Codes“
51. #WISSENTEILEN
// HTTP Status Codes:
// signal that there is more payload
// GET all orders on „page“ two
GET /orders?page=4 HTTP/1.1
[various other headers]
// Response with success code and links for navigation
HTTP/1.1. 206 Partial content
Link: <http://.../orders?page=1>; rel= „first“
<http://.../orders?page=3>; rel= „prev“,
...
REST
„Codes“
52. #WISSENTEILEN
// HTTP Status Codes:
// signal that payload is empty
// Ask for order with id 1234
GET /orders/1234 HTTP/1.1
[various other headers]
// Could not find order with id 1234
HTTP/1.1. 204 No content
REST
„Codes“
53. #WISSENTEILEN
// HTTP Status Codes:
// signal that payload is empty
// Ask for order with id 1234
GET /orders/1234 HTTP/1.1
[various other headers]
// Could not find order with id 1234
HTTP/1.1. 404 Not found
HTTP/1.1. 410 Gone
REST
„Codes“
54. #WISSENTEILEN
Manchmal kommt es anders, als man denk
• Code for Code: Status Code & Appliction Level Code
• Message for People: Für Logs, Ausgaben, ...
• Payload and Format: genormte Error-Payload Format
• Kein Stacktrace
• Exception Mapper als Provider auf äußerster Ebene
HTTP Status Codes
55. #WISSENTEILEN
// HTTP Status Codes:
// signal that there is a problem
// Error Responses: Use them wisely
HTTP/1.1 400 Bad Request (unknown/generic status)
HTTP/1.1 401 Unauthorized (btw: means unauthenticated)
HTTP/1.1 403 Forbidden (btw: means unauthorized)
HTTP/1.1 404 Not Found (resource could not be found)
HTTP/1.1 405 Method not … (POST, GET, ...)
HTTP/1.1 409 Conflict (fixable conflict, e.g. version)
HTTP/1.1 410 Gone (update cache)
HTTP/1.1 412 Precondition …(If-* header not matching)
HTTP/1.1 418 I‘am a teapot (Don‘t ask me for COFFEE!)
REST
„Codes“
56. #WISSENTEILEN
// HTTP Status Codes:
// JAX-RS Exception Handler
@Provider
public class MyAppExceptionHandler implements
ExceptionMapper<MyAppException> {
@Override
public Response toResponse(MyAppException exception) {
return Response.
status(Status.BAD_REQUEST).
entity(new ExceptionEntity(exception)).
build();
}
}
REST
„Codes“
57. #WISSENTEILEN
// HTTP Status Codes:
// signal that there is a problem
// Error Responses: Use them wisely
HTTP/1.1 429 To many request
HTTP/1.1 509 Bandwith limit exceeded
REST
„Codes“
58. #WISSENTEILEN
// HTTP Status Codes:
// Rate limit exceeded (e.g. Twitter)
HTTP/1.1 429 To many requests
[various other headers]
{
"errors": [
{ "code": 88,
"message": "Rate limit exceeded" }
]
}
REST
„Codes“
59. #WISSENTEILEN
// HTTP Status Codes:
// Rate limit exceeded (e.g. Twitter)
HTTP/1.1 200 Ok
[various other headers]
X-Rate-Limit-Limit: ... // rate limit ceiling
X-Rate-Limit-Remaining: ... // for the next 15 minutes
X-Rate-Limit-Reset: ... // in UTC epoch seconds
REST
„Codes“
61. #WISSENTEILEN
Always remember: „The Web is your Friend“
• das Web bietet tolle Möglichkeiten zur „Skalierung“
• RESTful Service nutzen das Web bzw. HTTP
• Client (Web Browser, REST Client, ...)
• Proxy Caches („man in the middle cache“)
• Content Delivery Networks (CDNs)
Caching
62. #WISSENTEILEN
// Caching in REST:
// Expires-Header (HTTP 1.0)
HTTP/1.1 200 Ok
Content-Type: application/json
Expires: Tue, 20 MAI 2017 12:00 GMT
{
"id": "espresso",
"displayName": "Espresso",
"price": 3.20,
...
}
REST
„Cache“
„Hint, ok. Aber für
wen eigentlich?“
63. #WISSENTEILEN
// Caching in REST:
// Chache-Control (HTTP 1.1)
HTTP/1.1 200 Ok
Content-Type: application/json
Cache-Control: private, no-store, max-age=3600
{
"id": "espresso",
"displayName": "Espresso",
"price": 3.20,
...
}
REST
„Cache“
„Only client side
caching. Valid for
3600 sec. Must not
be stored on disc.“
64. #WISSENTEILEN
// Caching in REST:
// Revalidation & Condition GET
// Cache-Control + Last-Modified Header HTTP 1.1
HTTP/1.1 200 Ok
Content-Type: application/json
Cache-Control: max-age=3600
Last-Modified: Wed, 10 MAI 2017 12:00 GMT
{
"id": "espresso",
...
}
REST
„Cache“
65. #WISSENTEILEN
// Caching in REST:
// Revalidation & Condition GET
// Conditional GET after Timeout (max-age)
GET /products/123 HTTP/1.1
If-Modified-Since: Wed, 10 MAI 2017 12:00 GMT
REST
„Cache“
Modified since? No,
304 (Not Modified).
Yes, 200 (Ok) plus
Data.
67. #WISSENTEILEN
// Caching in REST:
// Revalidation & Condition GET
// Conditional GET after Timeout (max-age)
GET /products/123 HTTP/1.1
If-Non-Match: "1234567890987654321"
REST
„Cache“
Modified since? No,
304 (Not Modified).
Yes, 200 (Ok) plus
Data.
74. #WISSENTEILEN
JSON Web Token
• neue, einfache Spec
• sehr kompakt
• Token plus public & private „Claims“
• digitale Signatur und/oder Encryption
• als Bearer Token und für SSO
Security
83. #WISSENTEILEN
Was ist das Problem?
• neue APIs
• geänderte APIs
• deprecaded APIs
• Payload / Parameter Syntax
• Payload / Parameter Semantik
API Evolution
84. #WISSENTEILEN
Postel‘s Law a.k.a. robustness principle:
„Be conservative in what you do*, be liberal in what you accept
from others.“
Ben Morris Blog:
„REST APIs don‘t need a versioning strategy – they need a
change strategy!“
API Evolution
(* do = change)
85. #WISSENTEILEN
Versionierung! Aber wie?
• gar nicht
• gar nicht (via neue Ressourcen)
• gar nicht (via erweiterbarer Datenformate)
• Versionsnummer in der URL
• Version Request Header
• Content Negotiation
API Evolution
86. #WISSENTEILEN
REST
„Version“
// Evolution in REST:
// Multiple Resources
// GET all orders v1
GET /orders HTTP/1.1
// GET all neworders, oders are deprecated
GET /neworders HTTP/1.1
// GET all even newer orders, new orders are deprecated
GET /evennewerorders HTTP/1.1
87. #WISSENTEILEN
REST
„Version“
// Evolution in REST:
// Multiple Resources
// GET all orders v1
GET /orders HTTP/1.1
// GET all neworders, oders are deprecated
GET /neworders HTTP/1.1
// GET all even newer orders, new orders are deprecated
GET /orders HTTP/1.1
88. #WISSENTEILEN
REST
„Version“
// Evolution in REST:
// Backwards compability
// Versioning via adaptable data format
{ "items" : [ {
"name" : "coffee",
"quantity" : 1,
"size" : "large",
} ],
"location" : ”take-away"
}
90. #WISSENTEILEN
REST
„Version“
// Evolution in REST:
// Backwards compability
// Versioning via adaptable data format
{ "items" : [ {
"name" : "coffee",
"quantity" : 1,
“size" : "large",
“price" : ”4 USD",
} ],
”price" : ”4 USD",
"location" : ”take-away"
}
„Are you a
tolerant
reader?“
91. #WISSENTEILEN
REST
„Version“
// Evolution in REST:
// via URL
// Versioning via URL (default version)
GET /api/orders/1234 HTTP/1.1
// Versioning via URL (version 1)
GET /api/v1/orders/1234 HTTP/1.1
// Versioning via URL (version 2)
GET /api/v2/orders/1234 HTTP/1.1
No way! This isn‘t a
RESOURCE!
92. #WISSENTEILEN
REST
„Version“
// Evolution in REST:
// via HEADER
// Versioning via Header(default version)
GET /orders/1234 HTTP/1.1
// Versioning via Custom Header
GET /orders/1234 HTTP/1.1
Api-version: 2.1
// Versioning via Accept Header
GET /orders/1234 HTTP/1.1
Accept: application/vnd.restbucks.orderservice.v2.1+json
No way! This isn‘t a
“clickable“ URL!
For chaching:
„Vary: Content-Type“
93. #WISSENTEILEN
REST
„Version“
// Evolution in REST:
// via URL and HEADER
// Versioning via Header(default version)
GET /orders/1234 HTTP/1.1
// Versioning via URL (major) and header (minor)
GET /orders/v2/1234 HTTP/1.1
my-api-version: 2017-05-01
94. #WISSENTEILEN
REST
„Hateoas“
„If the engine of application state (and hence the
API) is not driven by hypertext, then it cannot be
RESTful and cannot be a REST API.“
Roy Fielding
// Evolution in REST:
// Hypermedia as the engins of
// application state
95. #WISSENTEILEN
REST
„Hateoas“
„A REST API should be entered with no prior
knowledge beyond the initial URI ... From that
point on, all application state transitions must be
driven by the client selection of server-provides
choices ...“
Roy Fielding
// Evolution in REST:
// Hypermedia as the engins of
// application state
96. #WISSENTEILEN
REST
„ Hateoas“
// Evolution in REST:
// Hypermedia as the engins of
// application state
POST /orders/ HTTP/1.1
{ ... payload of order to create ... }
HTTP/1.1. 201 Created
Location: http://restbucks.com/api/orders/1234
Link: <.../orders/1234>; rel=„cancel“
<.../orders/1234>; rel=„update“,
<.../orders/1234>; rel=„delete“,
<.../payment/1234>; rel=„pay“
98. #WISSENTEILEN
„The very most important thing is that
you have an API that your consumers
find consistent and usable.
This is not necessarily the same thing
as being 100% RESTful.“