Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Consumer-centric API Design

158 views

Published on

APIs sind heutzutage der Kern von modernen Anwendungen. Wenn sie gut designt sind, können mit ihnen ohne großen Mehraufwand neue Geschäftsfelder erschlossen und neue oder geänderte Dienstleistungen angeboten werden.
Zudem werden immer mehr Systeme als Microservices-Architekturen aufgebaut, wodurch der Stellenwert auch von guten internen APIs wächst.
Je mehr Nutzer ein API hat und je weniger der Anbieter eines APIs seine Consumer kennt, um so wichtiger wird es, leicht verständliche und leicht zu verwendende APIs anzubieten, die auch sinnvoll weiterentwickelt werden können. Wie müssen sich Clients verhalten, damit sie die Schnittstelle auch bei Weiterentwicklung reibungslos verwenden können? Ansätze wie API First und Consumer-driven Contracts versuchen, diesen Anforderungen gerecht zu werden. Diese werden in dem Workshop an praktischen Beispielen vorgestellt. Zudem wird gezeigt, wie eine Schnittstelle abwärtskompatibel weiterentwickelt werden kann, ohne sich in der Versionierungshölle zu verlieren.

Published in: Software
  • Be the first to comment

Consumer-centric API Design

  1. 1. Consumer centric API Design Arrows designed by FreePik
  2. 2. ÜBER MICH Microservices maßgeschneidert | Arne Limburg • Enterprise Architect bei der open knowledge GmbH • Themen • Microservices • Domain Driven Design • APIs • Architektur • Coaching • Technologie (Java EE / Jakarta EE) Arne Limburg
  3. 3. ÜBER OPEN KNOWLEDGE Titel der Präsentation | Name des Sprechers Branchenneutrale Softwareentwicklung und IT-Beratung
  4. 4. Designing an API
  5. 5. Know the goal of the API
  6. 6. #WISSENTEILEN // Retrieve all products GET /products // What about searching, filtering, pagination? // Retrieve single product GET /products/1234 // What about buying it? // i.e. putting to shopping cart, payment, etc. REST „URLs“
  7. 7. Wisely choose HTTP Methods and Resources
  8. 8. #WISSENTEILEN // Retrieve all customers GET /customers // Retrieve single customer with ID 1234 GET /customers/1234 REST „URLs“
  9. 9. #WISSENTEILEN // Retrieve single customer with id 1234 GET /customers/1234 // Retrieve all addresses of customer 1234 GET /customers/1234/addresses // Retrieve address 5 of customer 1234 GET /customers/1234/addresses/5 REST „root“
  10. 10. #WISSENTEILEN // Retrieve all addresses of customer 1234 GET /customers/1234/addresses // vs. // Retrieve all addresses of customer 1234 GET /addresses/?customerNumber=1234 REST „root“
  11. 11. #WISSENTEILEN // Path parameter for identifier GET /customers/1234 // Query parameter for queries (optional) GET /customers/?status=verified // Header parameter for platform (optional) GET /customers/1234 Accept-Language: de-DE REST „param“
  12. 12. #WISSENTEILEN // Retrieve all addresses of customer 1234 GET /customers/1234/addresses // vs. // Retrieve all addresses of customer 1234 GET /addresses/?customerNumber=1234 REST „root“
  13. 13. #WISSENTEILEN // Body parameter for resource PUT /customers/1234 { ... CUSTOMER DATA ... } REST „param“
  14. 14. #WISSENTEILEN // register customer POST /customers { ... initial customers data ... } // read customer e.g. to check status GET /customers/1234 // change customer PUT /customers/1234 { ... some changes ... } // remove customer DELETE /customers/1234 REST „method“
  15. 15. #WISSENTEILEN // „Create new customer“. POST /customers // Is this allowed? Guess not. Is it? POST /customers/1234 // „Change customer“. I‘am sure! But wait ...“ PUT /customers/1234 // „Change customer“, too? Isn‘t it? PATCH /customers/1234 REST „method“
  16. 16. #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
  17. 17. #WISSENTEILEN // Creates customer. Returns ID 1234 POST /customers { ... payload for 1234 ... } // Hmmm, updates customer 1234? Creates new customer? POST /customers { ... payload for 1234 ... } REST „method“
  18. 18. #WISSENTEILEN // Changes customer with ID 1234 PUT /customers/1234 { ... changes for 1234 ... } // Hmmm, will be ignored? PUT /customers/1234 { ... changes for 1234, e.g. add e-mail address ... } // Or additional changes? PUT /customers/1234 { ... changes for 1234, e.g. add more e-mail addresses ... } REST „method“
  19. 19. Know your Audience
  20. 20. #WISSENTEILEN // Changes customer with ID 1234 PUT /customers/1234 { ... changes for 1234 ... } // Same as PUT? Mayby not! PATCH /customers/1234 { ... changes for 1234 ... } // Same as PUT? Is this what i expect? GET /customers/1234?status=verified or GET /customers/1234/setVerified REST „method“
  21. 21. #WISSENTEILEN SAFE / IDEMPOTENT Methods SAFE* keine Änderung an der Ressource IDEMPOTENT** einmalige Änderung an der Ressource-Repräsentation mehrfache Wiederholung führt immer zum selben Ergebnis * GET, HEAD, OPTIONS ** GET, HEAD, OPTIONS, PUT, DELETE
  22. 22. #WISSENTEILEN REST... Filter, Sorting & Pagination
  23. 23. #WISSENTEILEN // FILTERING: // List of paid orders (2015-12-20) // Common Style GET /orders?date=20151220&status=payed HTTP 1.1 REST „filter“
  24. 24. #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“
  25. 25. #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?
  26. 26. #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“
  27. 27. #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“
  28. 28. #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“
  29. 29. #WISSENTEILEN // Pagination a.k.a. „Limiting“: // 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?
  30. 30. #WISSENTEILEN // Pagination a.k.a. „Limiting“: // return page 4 of orders // get “page 4“ and info about PREV/NEXT GET /orders?offset=10&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“
  31. 31. #WISSENTEILEN // Pagination a.k.a. „Limiting“: // 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“
  32. 32. #WISSENTEILEN // FULL TEXT SEARCH: // Fulltext search for coffee // Global style via virtual resource GET /searches?q=coffee HTTP/1.1 oder // Scoped style GET /orders/searches?q=coffee HTTP/1.1 GET /orders?q=coffee HTTP/1.1 REST „search“ „GET vs. POST „GET vs. POST
  33. 33. #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?
  34. 34. #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“?
  35. 35. #WISSENTEILEN // ADVANCED SEARCH: // Coffee WITH milk or 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“
  36. 36. Use Standards Standards promote efficiency, trust and safety https://www.din.de/en/about-standards/use-standards
  37. 37. #WISSENTEILEN Caching
  38. 38. #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
  39. 39. #WISSENTEILEN // Caching in REST: // Expires-Header (HTTP 1.0) HTTP/1.1 200 Ok Content-Type: application/json Expires: Mon, 24 SEP 2018 12:00:01 GMT { "id": "1", "firstName": "Max", "lastName": "Mustermann", ... ... } REST „Cache“ „Hint, ok. Aber für wen eigentlich?“
  40. 40. #WISSENTEILEN CACHING Cache-Control (HTTP 1.1) • deutlich genauere Cache-Steuerung als bei Expires Header • private, public • no-cache • no-store • no-transform • max-age, s-maxage
  41. 41. #WISSENTEILEN // Caching in REST: // Cache-Control (HTTP 1.1) HTTP/1.1 200 Ok Content-Type: application/json Cache-Control: private, no-store, max-age=3600 { "id": "1", "firstName": "Max", "lastName": "Mustermann", ... } REST „Cache“ „Only client side caching. Valid for 3600 sec. Must not be stored on disc.“
  42. 42. #WISSENTEILEN CACHING Revalidation & Conditional GET (HTTP 1.1) • Revalidation zur Prüfung, ob Cache-Daten wirklich invalide • Server sendet speziellen Header zur Prüfung zurück • Last-Modified • Etag
  43. 43. #WISSENTEILEN // Caching in REST: // Revalidation & Conditional 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:01 GMT { "id": "1", ... } REST „Cache“
  44. 44. #WISSENTEILEN // Caching in REST: // Revalidation & Conditional GET // Conditional GET after Timeout (max-age) GET /products/123 HTTP/1.1 If-Modified-Since: Wed, 10 MAI 2017 12:00:01 GMT REST „Cache“ Modified since? No, 304 (Not Modified). Yes, 200 (Ok) plus Data.
  45. 45. #WISSENTEILEN // Caching in REST: // Revalidation & Conditional GET // Cache-Control + eTag Header HTTP 1.1 HTTP/1.1 200 Ok Content-Type: application/json Cache-Control: max-age=3600 eTag: "1234567890987654321" { "id": "1", ... } REST „Cache“
  46. 46. #WISSENTEILEN // Caching in REST: // Revalidation & Condition GET // Conditional GET after Timeout (max-age) GET /products/123 HTTP/1.1 If-None-Match: "1234567890987654321" REST „Cache“ Modified since? No, 304 (Not Modified). Yes, 200 (Ok) plus Data.
  47. 47. #WISSENTEILEN // Optimistic Locking // Conditional PUT // Conditional PUT PUT /customers/1234 HTTP/1.1 If-Match: "1234567890987654321" REST „Cache“ Modified in between? No, 200 (OK). Yes, 412 (Precondition Failed)
  48. 48. #WISSENTEILEN // Optimistic Locking // Conditional PUT // PUT ohne Precondition PUT /customers/1234 HTTP/1.1 REST „Cache“ Force Optimistic Locking: 428 (Precondition Required)
  49. 49. #WISSENTEILEN REST ... Headers
  50. 50. #WISSENTEILEN Headers HTTP Header Parameter • Meta Daten für Request / Response • Accept: ich komme mit Format xyz klar • Content-Type: ich sende dir Format xyz • Location-Header: genauere/mehr Info zu „mir“ unter ... • Link-Header: siehe auch unter ... • Authorization*-Header: Auth Tokens, Basic Auth ... • X-Custom-Header: ich habe da noch was für dich ... {?}
  51. 51. #WISSENTEILEN REST... Status Codes (see also: http://www.restpatterns.org/HTTP_Status_Codes/)
  52. 52. #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)
  53. 53. #WISSENTEILEN // HTTP Status Codes: // signal that new order was created // „Create new Resource“ of type order POST /customers HTTP/1.1 [various other headers] // Response with location header pointing to resource HTTP/1.1. 201 Created Location: http://…/customers/1234 REST „Codes“
  54. 54. #WISSENTEILEN // HTTP Status Codes: // signal that some work is going on // Trigger some async workload POST /customers/ HTTP/1.1 [various other headers] // Response without payload, cause it‘s not yet calculated HTTP/1.1. 202 Accepted REST „Codes“
  55. 55. #WISSENTEILEN // HTTP Status Codes: // signal that payload is empty // DELETE customer with id 1234 DELETE /customers/1234 HTTP/1.1 [various other headers] // Customer successfully deleted. No content by purpose HTTP/1.1. 204 No content HTTP/1.1. 205 Reset content REST „Codes“
  56. 56. #WISSENTEILEN // HTTP Status Codes: // signal that payload is empty // Retrieve all verified customers GET /customers?status=verified HTTP/1.1 [various other headers] // There is no verified customer left. HTTP/1.1. 204 No content REST „Codes“
  57. 57. #WISSENTEILEN // HTTP Status Codes: // signal that there is more payload // GET all orders on „page“ two GET /customers?page=4 HTTP/1.1 [various other headers] // Response with success code and links for navigation HTTP/1.1. 206 Partial content Link: <http://.../customers?page=1>; rel= „first“ <http://.../customers?page=3>; rel= „prev“, ... REST „Codes“
  58. 58. #WISSENTEILEN // HTTP Status Codes: // signal that could not find order // Ask for customer with id 1234 GET /customers/1234 HTTP/1.1 [various other headers] // Could not find customer with id 1234 HTTP/1.1. 404 Not found REST „Codes“
  59. 59. #WISSENTEILEN // HTTP Status Codes: // signal that there is a limit problem // Error Responses: Use them wisely HTTP/1.1 429 To many request HTTP/1.1 509 Bandwith limit exceeded REST „Codes“
  60. 60. #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“
  61. 61. #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“
  62. 62. #WISSENTEILEN // HTTP Status Codes: // signal that the payload is invalid // Change customer with id 1234 PUT /customers/1234 HTTP/1.1 [various other headers] // Some content is invald HTTP/1.1. 400 Bad Request REST „Codes“
  63. 63. #WISSENTEILEN // HTTP Status Codes: // Bad request // - Validation error with problem+json HTTP/1.1 400 Bad request Content-Type: application/problem+json [various other headers] { "type": "/problems/validation-error", "title": "Your request parameters didn't validate.", "status": 400, "detail": "...", "instance": "orders/1234" } REST „Codes“
  64. 64. #WISSENTEILEN // HTTP Status Codes: // Bad request // - Validation error with problem+json HTTP/1.1 400 Bad request Content-Type: application/problem+json [various other headers] { "type": "/problems/validation-error", "title": "Your request parameters didn't validate.", ... "invalid-params": [ { "path": "firstName", "message": "firstName is mandatory" } ] } Custom Extension for Problem JSON REST „Codes“
  65. 65. #WISSENTEILEN Manchmal kommt es anders, als man denk • Code for Code: Status Code & Application Level Code • Message for People: Für Logs, Ausgaben, ... • Payload and Format: genormte Error-Payload Format HTTP Status Codes
  66. 66. #WISSENTEILEN // „Create new customer“. POST /customers Content-Type: application/json // Is this allowed? Guess not. Is it? POST /customers/1234 Content-Type: application/json // „Change customer“. I‘am sure! But wait ...“ PUT /customers/1234 Content-Type: application/json // „Change customer“, too? Isn‘t it? PATCH /customers/1234 Content-Type: application/json REST „method“
  67. 67. #WISSENTEILEN // HTTP Status Codes: // Which patch should be used HTTP/1.1 415 Unsupported Media Type Content-Type: application/problem+json [various other headers] { "type": "/problems/unsupported-media-type", "detail": "supported media types are application/merge-patch+json or application/json-patch+json", } REST „Payload“
  68. 68. #WISSENTEILEN // HTTP PATCH: // json/merge-patch+json POST /customers Content-Type: application/json [various other headers] { "customerNumber": "1234", "name": { "firstName": "Max", "lastName": "Mustermann" }, "email": "max.mustermann@beispiel.de" } REST „Payload“
  69. 69. #WISSENTEILEN // HTTP PATCH: // json/merge-patch+json // leave customer number and first name unchanged // change last name, remove email PATCH /customers/1234 Content-Type: application/merge-patch+json [various other headers] { "name": { "lastName": "Muster" }, "email": null } REST „Payload“
  70. 70. #WISSENTEILEN // HTTP PATCH: // json/json-patch+json PATCH /customers/ Content-Type: application/json-patch+json [various other headers] [ { "op": "remove", "path": "/1234/email" }, { "op": "replace", "path": "/1234/name/last", "value": "Muster" }, { "op": "add", "path": "/1234/name/additional", "value": "Thomas" }, { "op": "move", "from": "/1234", "path": "/1235" }, ] REST „Payload“
  71. 71. #WISSENTEILEN „Leonard Richardson proposed a classification for services on the Web. Leonard’s model promotes three levels of service maturity based on a service’s support for URIs, HTTP, and hypermedia!“ (Quelle: „REST in Practice“)
  72. 72. #WISSENTEILEN RICHARTSON MATURITY MODEL 0 1 2 3 Hypermedia URI HTTP
  73. 73. #WISSENTEILEN RICHARTSON MATURITY MODEL 0 1 2 3 Hypermedia URI HTTP
  74. 74. Titel der Präsentation | Name des Sprechers RICHARTSON MATURITY MODEL Level 0 • a.k.a. „Swamp of PoX“: • HTTP als Transportsystem • „RPC“-Style • eine URI • eine HTTP Methode (meist POST) • Parameter und Rückgabe als Payload (XML/JSON/TEXT)
  75. 75. #WISSENTEILEN RICHARTSON MATURITY MODEL 0 1 2 3 Hypermedia URI HTTP
  76. 76. Titel der Präsentation | Name des Sprechers RICHARTSON MATURITY MODEL Level 1 • Aufbrechen des monolithischen Endpoints aus Level 0 in unterschiedliche Ressourcen (a.k.a. Nomen), die gezielt angesprochen werden können.
  77. 77. Titel der Präsentation | Name des Sprechers RICHARTSON MATURITY MODEL Level 1 • a.k.a. „Resources“ • Individuelle Ressourcen statt Service Endpoint • mehrere URIs • ein oder zwei HTTP Methode (meist POST/GET) • Parameter als Query-Parameter oder Payload • Rückgabe als Payload (XML/JSON/TEXT)
  78. 78. #WISSENTEILEN RICHARTSON MATURITY MODEL 0 1 2 3 Hypermedia URI HTTP
  79. 79. Titel der Präsentation | Name des Sprechers RICHARTSON MATURITY MODEL Level 2 • Einführen verschiedener „Verben“, um gleiche Situationen mit den selben Mechanismen behandeln zu können und so unnötige Varianten zu vermeiden.
  80. 80. Titel der Präsentation | Name des Sprechers RICHARTSON MATURITY MODEL Level 2 • a.k.a. „Verbs“ • „passende“ HTTP Methoden statt nur POST & GET • mehrere URIs • mehrere HTTP Methoden • Rückgabe als Payload & Status Codes
  81. 81. #WISSENTEILEN RICHARTSON MATURITY MODEL 0 1 2 3 Hypermedia URI HTTP
  82. 82. Titel der Präsentation | Name des Sprechers RICHARTSON MATURITY MODEL Level 3 • Einführen von „Auffindbarkeit“, um so einen Mechanismus anzubieten, der das Protokoll selbsterklärend(er) macht.
  83. 83. Titel der Präsentation | Name des Sprechers RICHARTSON MATURITY MODEL Level 3 • a.k.a. „Hypermedia“ • selbsterklärendes System • nur eine Einstiegs-URI bekannt • keine weiteren festen URIs • Rückgabe als Liste von neuen „Möglichkeiten“
  84. 84. Make your API explorable
  85. 85. #WISSENTEILEN „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. Period. Is there some broken manual somewhere that needs to be fixed?“ Roy Fielding
  86. 86. #WISSENTEILEN „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
  87. 87. Titel der Präsentation | Name des Sprechers RICHARTSON MATURITY MODEL /orders/... POST /orders { order payload } http/1.1 201 Created Location: ... Link: cancel operation update operation delete operation pay operation C O N S U M E R O R D E R S
  88. 88. Titel der Präsentation | Name des Sprechers RICHARTSON MATURITY MODEL /orders/... /payments/... http/1.1 201 Created Location: ... Link: http://<status operation> http://<update operation> http://<delete operation> http://<pay operation> GET PUT DELETE POST O R D E R P A Y
  89. 89. #WISSENTEILEN REST „Hateoas“ v1 // Richardson Maturity Model: // Hypermedia as the engine // 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“
  90. 90. #WISSENTEILEN // Aber wie bilde ich Relationen ab? // the „naive“ way // all customers { [ { "id":"001", "for":"arne", ..., "addresses":[ ...] }, { "id":"002", "for":"lars", ..., "addresses":[ ...] }, ... ] } REST „relation“
  91. 91. #WISSENTEILEN // Aber wie bilde ich Relationen ab? // the „naive“ way // all customers { [ { "id":"001", "for":"arne", ..., "addresses":[ ...] }, { "id":"002", "for":"lars", ..., "addresses":[ ...] }, ... ] } REST „relation“ Benötige ich die Info hier wirklich?
  92. 92. #WISSENTEILEN // Aber wie bilde ich Relationen ab? // the „linked“ way // all customers { [ { "id":"001", "for":"arne", ..., "address_refs":[...]}, { "id":"002", "for":"lars", ..., "address_refs":[...] }, ... ] } REST „relation“ https://..../customers/002/addresses/1 https://..../customers/002/addresses/2 Benötige ich weitere Infos?
  93. 93. #WISSENTEILEN // Aber wie bilde ich Relationen ab? // the „linked“ way // all customers { [ { "id":"001", "for":"lars", ..., "addresses_refs":[...], "addresses_count" : 2 ] } REST „relation“
  94. 94. #WISSENTEILEN // Aber wie bilde ich Relationen ab? // the „linked“ way // all customers { [ { "id":"001", "for":"lars", "for_ref ": "http://.../customers/12", ..., "addresses_refs":[...], "addresses_count" : 2 ]} REST „relation“
  95. 95. #WISSENTEILEN // Aber wie bilde ich Relationen ab? // the „linked“ way // all customers { [ { "id":"001", "for":"arne", ..., "address":[ { "href": "http://.../customers/001/addresses/1" } ]}, ... ] } REST „relation“ Welche Syntax hilft dem Consumer?
  96. 96. #WISSENTEILEN Content-Type: application/hal+json { "_embedded": { "customers": [{ "_links": { "self": { "href": "/customers/001“ }, … }, "name": "Arne Limburg", … }, …]}, "_links": { "next": … "find": {"href": "/customers{?tags}", "templated": true} }, … } REST „relation“
  97. 97. Document your API
  98. 98. API First
  99. 99. #WISSENTEILEN DOCUMENTATION OpenAPI Specification (OAS) Design mit Swagger Editor Build mit Swagger Codegen Dokumentiere mit Swagger-UI
  100. 100. #WISSENTEILEN // openapi.json // JSON-Schema / OpenAPI Example "post" : { "parameters" : [ { "in" : "body", "name" : "body", "description" : "new customer", "required" : true, "schema" : { "$ref" : "#/definitions/Customer" } } ] } REST „OpenAPI“
  101. 101. #WISSENTEILEN // openapi.json (JSON Schema) "definitions" : { "CustomerResourceType" : { "type" : "object", "required" : [ "firstName", "lastName”, … ], "properties" : { "firstName" : { "type" : "string", "minLength" : 1, "maxLength" : 30, "pattern" : "..." } } REST „OpenAPI“
  102. 102. Evolve your API
  103. 103. MICROSERVICES ARCHITEKTUR Address Validation Service Delivery Service Customer Service Billing Service
  104. 104. Tolerant Reader Pattern Tolerant gegenüber unbekannten Feldern Umgang mit x-extensible-enum http://zalando.github.io/restful-api-guidelines
  105. 105. EXKURS X-EXTENSIBLE-ENUM Beispiel address_type: type: string x-extensible-enum: - private - business
  106. 106. Tolerant Reader Pattern Tolerant gegenüber unbekannten Feldern Umgang mit x-extensible-enum http://zalando.github.io/restful-api-guidelines Tolerant gegenüber unbekannten Statuscodes HTTP Status 301 folgen
  107. 107. TOLERANT READER PATTERN http://www.example.com/addresses/42 àLiefert Addresse { street: { "name": "Poststraße", "number": "1", }, "city": "26122 Oldenburg" }
  108. 108. TOLERANT READER PATTERN http://www.example.com/addresses/42 Attribut hinzufügen { street: { "name": "Poststraße", "number": "1", "additionalAdressLine": "2. Obergeschoss" }, "city": "26122 Oldenburg" }
  109. 109. TOLERANT READER PATTERN Server schickt { street: { "name": "Poststraße", "number": "1", "additionalAdressLine": "2. Obergeschoss" }... } http://www.example.com/addresses/42 Client erwartet { street: { "name": "Poststraße", "number": "1", }... }
  110. 110. TOLERANT READER PATTERN http://www.example.com/addresses/42 Attribut-Umbenennung { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", }, "city": "26122 Oldenburg" }
  111. 111. TOLERANT READER PATTERN http://www.example.com/addresses/42 àAbwärtskompatible Änderungen: Umbenennen durch Kopieren { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", }, "city": "26122 Oldenburg" }
  112. 112. Und was ist, wenn der Client kein Tolerant Reader ist?
  113. 113. PROJEKTIONEN // Client kann entscheiden, welche Felder er bekommen möchte // Facebook Style GET /addresses/3?fields=street,city GET /addresses/3?fields=street.name,street.number,city // LinkedIn Style GET /addresses/3?fields=street:(name,number),city
  114. 114. OK, so soll sich der Client verhalten. Aber was ist mit dem Server?
  115. 115. Photo by Irene Fertik, USC News Service. Copyright 1994, USC. „Be conservative in what you do, Be liberal in what you accept from others“ RFC 793, Robustness Principal (John Postel)
  116. 116. Es darf nichts entfernt werden Keine Veränderung von Verarbeitungsregel Optionales darf nie Required werden http://zalando.github.io/restful-api-guidelines Alles was hinzugefügt wird, muss optional sein
  117. 117. MAGNANIMOUS WRITER PATTERN Server schickt { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1“ }... } http://www.example.com/addresses/42 Client erwartet { street: { "name": "Poststraße", "number": "1", }... } http://tenderware.blogspot.de/2011/05/magnanimous-writer.html
  118. 118. MAGNANIMOUS WRITER PATTERN Server schickt { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1“ }... } http://www.example.com/addresses/42 Client erwartet { street: { "streetName": "Poststraße", "houseNumber": "1", }... } http://tenderware.blogspot.de/2011/05/magnanimous-writer.html
  119. 119. MAGNANIMOUS WRITER PATTERN Server erwartet PUT http://www.example.com/addresses/42 Client schickt { street: { "name": "Poststraße", "number": "1", }... } http://tenderware.blogspot.de/2011/05/magnanimous-writer.html
  120. 120. MAGNANIMOUS WRITER PATTERN PUT http://www.example.com/addresses/42 Client schickt { street: { "name": "Poststraße", "number": "1", }... } http://tenderware.blogspot.de/2011/05/magnanimous-writer.html Server erwartet { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1“ }... } oder oder
  121. 121. MAGNANIMOUS WRITER PATTERN PUT http://www.example.com/addresses/42 Client schickt { street: { "streetName": "Poststraße", "houseNumber": "1", }... } http://tenderware.blogspot.de/2011/05/magnanimous-writer.html Server erwartet { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1“ }... } oder oder
  122. 122. MERGE PATCH UND NULL VALUES PATCH /addresses/42 HTTP/1.1 Content-Type: application/merge-patch+json Client schickt { street: { "name": null, }... } https://tools.ietf.org/html/rfc7396
  123. 123. ABWÄRTSKOMPATIBILITÄT http://www.example.com/addresses/42 àAbwärtskompatible Änderungen: Umbenennen durch Kopieren { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", }, "city": "26122 Oldenburg" } Geht da noch mehr?
  124. 124. ABWÄRTSKOMPATIBILITÄT http://www.example.com/addresses/42 àZusammenführen und Teilen von Attributen { street: { ... "streetName": "Poststraße", "houseNumber": "1", "addressLine1": "Poststraße 1", } ... }
  125. 125. ABWÄRTSKOMPATIBILITÄT http://www.example.com/addresses/42 àHerausforderungen: Zusammenführen von Attributen { street: { ... "streetName": "Poststraße", "houseNumber": "1", "addressLine1": "Poststraße 1", } ... }
  126. 126. ABWÄRTSKOMPATIBILITÄT http://www.example.com/addresses/42 àHerausforderungen: Teilen von Attributen { street: { ... "addressLine1": "Poststraße 1", } "city": "26122 Oldenburg", "zipCode": "26122", "cityName": "Oldenburg" }
  127. 127. ABWÄRTSKOMPATIBILITÄT http://www.example.com/addresses/42 àHerausforderungen: Ebene von Attributen ändern { street: { ... "addressLine1": "Poststraße 1" } "addressLine1": "Poststraße 1", ... }
  128. 128. ABWÄRTSKOMPATIBILITÄT http://www.example.com/addresses/42 àHerausforderungen: Ebene von Attributen ändern { ... "zipCode": "26122", "cityName": "Oldenburg", "location": { "zipCode": "26122", "cityName": "Oldenburg" } }
  129. 129. ABWÄRTSKOMPATIBILITÄT http://www.example.com/addresses/42 àHerausforderungen: Ebene von Attributen ändern { ... "zipCode": "26122", "cityName": "Oldenburg", "location": { "zipCode": "26122", "cityName": "Oldenburg" } } Bei jeder Modelländerung muss eine Migrationsstrategie einbezogen werden!
  130. 130. ABWÄRTSKOMPATIBILITÄT { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" } "addressLine1": "Poststraße 1", "addressLine2": "", "city": "26122 Oldenburg", "zipCode": "26122", "cityName": "Oldenburg", "location": { "zipCode": "26122", "cityName": "Oldenburg" } } http://www.example.com/v1/addresses/42 àBisher nur abwärtskompatible Änderungen
  131. 131. ABWÄRTSKOMPATIBILITÄT { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" } "addressLine1": "Poststraße 1", "addressLine2": "", "city": "26122 Oldenburg", "zipCode": "26122", "cityName": "Oldenburg", "location": { "zipCode": "26122", "cityName": "Oldenburg" } } http://www.example.com/v1/addresses/42 àViele Attribute deprecated
  132. 132. Wie kann ich Attribute entfernen?
  133. 133. Don‘t ever break your Client
  134. 134. Incompatible Change vs. Breaking Change http://zalando.github.io/restful-api-guidelines
  135. 135. WELCHE CLIENTS NUTZEN MICH?
  136. 136. API (PROVIDER CONTRACT)
  137. 137. CONSUMER CONTRACT
  138. 138. CONSUMER CONTRACT – YAGNI
  139. 139. CONSUMER-DRIVEN CONTRACT TEST Consumer Contract Consumer Provider Consumer Tests Provider Tests
  140. 140. MICROSERVICES ARCHITEKTUR Address Validation Service Delivery Service Customer Service Billing Service
  141. 141. PIPELINE TO DEPLOY TO STAGE Execute Own Provider Tests Generate Consumer Contract Execute Depending Provider Tests Deploy to Stage
  142. 142. PIPELINE TO DEPLOY TO STAGE Execute Own Provider Tests Generate Consumer Contract Execute Depending Provider Tests Deploy to Stage Achtung: Abwärtskompatibilität ist trotzdem notwendig!
  143. 143. BREAKING CHANGE VOM PROVIDER Execute Own Provider Tests Generate Consumer Contract Execute Depending Provider Tests Deploy to Stage
  144. 144. BREAKING CHANGE VOM CONSUMER Execute Own Provider Tests Generate Consumer Contract Execute Depending Provider Tests Deploy to Stage
  145. 145. Und wenn ich meine Consumer nicht kenne?
  146. 146. VERSIONSSPRUNG { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" } "addressLine1": "Poststraße 1", "addressLine2": "", "city": "26122 Oldenburg", "zipCode": "26122", "cityName": "Oldenburg", "location": { "zipCode": "26122", "cityName": "Oldenburg" } } http://www.example.com/v1/addresses/42 àBisher nur abwärtskompatible Änderungen
  147. 147. INKOMPATIBLE ÄNDERUNG http://www.example.com/v2/addresses/42 àVersionssprung { "addressLine1": "Poststraße 1", "addressLine2": "", "location": { "zipCode": "26122", "cityName": "Oldenburg" } }
  148. 148. INKOMPATIBLE ÄNDERUNG
  149. 149. Version 2.0 ist nur inkompatibel zu 1.0! Version 2.0 ist identisch zu 1.x! Das erleichtert das Mapping zwischen den Versionen!
  150. 150. Ein solcher Versionssprung ist nicht anforderungsgetrieben, sondern viel besser und langfristiger planbar
  151. 151. Wenn eine neue Version nicht auf die alte abbildbar ist, ist es keine neue Version, sondern eine neue Schnittstelle!
  152. 152. • Über URL-Pfad /v2/addresses • Über Query-Parameter /addresses?version=v2 • Über Version-Header X-Api-Version: v2 • Über Version-Attribut am Media-Type application/xml;version=v2 • Über Media-Type application/vnd.de.openknowledge+v2+json ERMITTELN DER VERSION
  153. 153. ABWÄRTSKOMPATIBILITÄT Schnittstelle Schnittstelle { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" } "city": "26122 Oldenburg", "zipCode": "26122", "cityName": "Oldenburg", "addressLine1": "Poststraße 1", "addressLine2": "", "location": { "zipCode": "26122", "cityName": "Oldenburg" } } { "addressLine1": "Poststraße 1", "addressLine2": "“, "location": { "zipCode": "26122", "cityName": "Oldenburg" } } V1 V2-SNAPSHOT
  154. 154. ABWÄRTSKOMPATIBILITÄT Schnittstelle Schnittstelle { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" }, "city": "26122 Oldenburg", "zipCode": "26122", "cityName": "Oldenburg", "addressLine1": "Poststraße 1", "addressLine2": "“, "location": { "zipCode": "26122", "cityName": "Oldenburg" } } { "addressLine1": "Poststraße 1", "addressLine2": "", "location": { "zipCode": "26122", "cityName": "Oldenburg" } } V1 V2-SNAPSHOT
  155. 155. AUTOMATISIERTES MAPPING Schnittstelle Schnittstelle { "street": { "name": "Poststraße", "number": "1", }, "city": "26122 Oldenburg" } { "street": { "name": "Poststraße", "number": "1" }, "city": "26122 Oldenburg" } V1 V2-SNAPSHOT
  156. 156. WEITERENTWICKLUNG Schnittstelle Schnittstelle { "street": { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", }, "city": "26122 Oldenburg" } { "street": { "streetName": "Poststraße", "houseNumber": "1" }, "city": "26122 Oldenburg" } V1 V2-SNAPSHOT
  157. 157. WEITERENTWICKLUNG Schnittstelle Schnittstelle { "street": { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" }, "city": "26122 Oldenburg" } { "street": { "addressLine1": "Post… 1", "addressLine2": "" }, "city": "26122 Oldenburg" } V1 V2-SNAPSHOT
  158. 158. WEITERENTWICKLUNG Schnittstelle Schnittstelle { "street": { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" }, "addressLine1": "Poststraße 1", "addressLine2": "", "city": "26122 Oldenburg", "zipCode": "26122", "cityName": "Oldenburg", } { "addressLine1": "Poststraße 1", "addressLine2": "", "zipCode": "26122", "cityName": "Oldenburg" } V1 V2-SNAPSHOT
  159. 159. WEITERENTWICKLUNG Schnittstelle Schnittstelle { "street": { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" }, "addressLine1": "Poststraße 1", "addressLine2": "", "city": "26122 Oldenburg", "zipCode": "26122", "cityName": "Oldenburg", "location": { "zipCode": "26122", "cityName": "Oldenburg" } } { "addressLine1": "Poststraße 1", "addressLine2": "", "location": { "zipCode": "26122", "cityName": "Oldenburg" } } V1 V2-SNAPSHOT
  160. 160. ABWÄRTSKOMPATIBILITÄT Schnittstelle Schnittstelle { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" }, "city": "26122 Oldenburg", "zipCode": "26122", "cityName": "Oldenburg", "addressLine1": "Poststraße 1", "addressLine2": "“, "location": { "zipCode": "26122", "cityName": "Oldenburg" } } { "addressLine1": "Poststraße 1", "addressLine2": "", "location": { "zipCode": "26122", "cityName": "Oldenburg" }, "city": { "zipCode": "26122", "cityName": "Oldenburg" } } V1 V2-SNAPSHOT
  161. 161. ABWÄRTSKOMPATIBILITÄT Schnittstelle Schnittstelle { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" } ... } { "addressLine1": "Poststraße 1", "addressLine2": "", "location": { "zipCode": "26122", "cityName": "Oldenburg" } } V1 V3-SNAPSHOT Schnittstelle { "addressLine1": "Poststraße 1", "addressLine2": "“, "location": { "zipCode": "26122", "cityName": "Oldenburg" } } V2
  162. 162. ABWÄRTSKOMPATIBILITÄT Schnittstelle Schnittstelle { street: { "name": "Poststraße", "streetName": "Poststraße", "number": "1", "houseNumber": "1", "addressLine1": "Post... 1", "addressLine2": "" } ... } { "addressLine1": "Poststraße 1", "addressLine2": "", "city": { "zipCode": "26122", "cityName": "Oldenburg" } } V1 V3-SNAPSHOT Schnittstelle { "addressLine1": "Poststraße 1", "addressLine2": "“, "location": { "zipCode": "26122", "cityName": "Oldenburg" }, "city": { "zipCode": "26122", "cityName": "Oldenburg" }} V2
  163. 163. KONTAKT Titel der Präsentation | Name des Sprechers ARNE LIMBURG, ENTERPRISE ARCHITECT arne.limburg@openknowledge.de +49 (0)441 40820 OFFENKUNDIGGUT
  164. 164. FRAGEN & DISKUSSION ? ? ?

×