Building Resilient API Clients (SLUG)

Building Resilient API Clients (SLUG)

An introduction to building API clients and servers which can evolve independently. This talk will cover connecting to an API without upfront knowledge about any implementation details such as URIs, HTTP "verbs" and content types but instead an understanding of the semantic meaning of what the API does. By leaning controls on the fly, we can allow a client and server to change independently over time.

D200a17dd269fd4001bacb11662dab4b?s=128

Kyle Fuller

June 25, 2015
Tweet

Transcript

  1. Building!Resilient!API!Clients !KyleFuller

  2. None
  3. None
  4. Building!Resilient!API!Clients

  5. None
  6. None
  7. None
  8. None
  9. None
  10. None
  11. How$does$it$work?

  12. GET /questions

  13. DELETE /questions/{id}

  14. GET /questions/{id}

  15. POST /questions/{id}/choices/{choice_id}

  16. POST /questions

  17. Applica'on*was*built*from*an*out3 of3band*specifica'on

  18. Tight&Coupling

  19. We#want#to#change#something.

  20. /v1

  21. /v2

  22. Move%Fast%and%Break%Things

  23. Move%Fast%and%Break%Things

  24. None
  25. None
  26. "Each&version&will&remain&for&at&least& 2&years&from&release."

  27. Eventually*have*to*switch

  28. API$Team$maintains$legacy$API

  29. Move%Fast%and%Break%Nothing

  30. REST

  31. What%is%REST?

  32. “An$cipa$ng*change*is*one*of*the* central*themes*of*REST”

  33. REST%is%not... • CRUD • Pre)y+URLs • JSON • HTTP+Verbs •

    Rou8ng
  34. It’s%not%about%exposing%your% database%via%an%API

  35. These%are%implementa)on%details

  36. Representa)onal,State,Transfer

  37. Transfer(State(from(Server(to(Client?

  38. Ques%on(Resource

  39. HATEOAS

  40. None
  41. Hypermedia*as*the*Engine*of* Applica6on*State

  42. Ques%ons • create • list

  43. Ques%on • delete • choices

  44. Choice • vote

  45. Teaching)the)client)the)seman.c) meaning)of)the)domain

  46. Without'knowledge'about'the' implementa3on'details

  47. Root questions

  48. Ques%on delete

  49. Choice vote

  50. Ques%on create • ques&on • choices

  51. Hyperdrive

  52. How$do$we$get$run,-me$client$ informa-on$in$our$clients?

  53. One$Does$Not$Simply$Use$JSON

  54. HAL$+$Siren

  55. HAL Hypertext(Applica.on(Language

  56. None
  57. HAL { "_links": { "question": { "href": "/questions/1" } }

    }
  58. HAL { "_embedded": { "choices": [ { "_links": { "self":

    { "self": "/questions/1/choices/1" } }, "choice": "Swift", "votes": 2048 } ] } }
  59. None
  60. Siren { "actions": [ { "name": "vote", "href": "/questions/1/choices/3", "method":

    "POST" } ] }
  61. Siren { "actions": [ { "name": "create", "fields": [ {

    "name": "question", "type": "text", "title": "Question" }, { "name": "choices", "type": "array[text]", "title": "Choices" } ], "method": "POST", "type": "application/json", "href": "/questions" } ] }
  62. API$Blueprint$+$Hyperdrive

  63. apiblueprint.org

  64. None
  65. hyperdrive.enter("https://polls.apiblueprint.org/") { result }

  66. representor

  67. None
  68. representor • a#ributes • transi,ons

  69. representor • a#ributes • transi,ons • representors

  70. if let questions = representor.transitions["questions"] { }

  71. hyperdrive.request(questions) { result in }

  72. if let questions = representor.representors["questions"] { map(questions, viewQuestion) }

  73. } else { println("Looks like there are no questions yet.")

    }
  74. func viewQuestion(question:Representor<HTTPTransition>) { println(question.attributes["question"]) if let choices = question.representors["choices"] {

    map(choices, viewChoice) } else { println("-> This question does not have any choices.") } if let delete = question.transitions["delete"] { // User may delete this question } }
  75. func viewChoice(choice:Representor<HTTPTransition>) { let text = choice.attributes["choice"] let votes =

    choice.attributes["votes"] println('-> \(text) (\(votes))') if let vote = choice.transitions["vote"] { // User may vote on this choice } }
  76. hyperdrive.request(vote)

  77. if let create = questions.transitions["create"] { // We may create

    a new question for attribute in create.attributes { // Creation takes `attribute.name` } } else { // Gracefully handle the lack of being able to create a question }
  78. Why?

  79. Change'business'rules

  80. Remove&transi-ons

  81. Add#transi*ons

  82. Coupling

  83. TaaS Toaster(as(a(Service

  84. Toaster(Applica.on

  85. None
  86. None
  87. None
  88. Demo

  89. How$does$API$Blueprint$work?

  90. ## Questions Collection [/questions] ### List All Questions [GET] +

    Relation: questions + Response 200 (application/json) + Attributes (array[Question])
  91. ### Create a New Question [POST] + Relation: create +

    Request (application/json) + Attributes + question (string, required) - The question + choices (array[string]) - A collection of choices. + Response 201 (application/json) + Attributes (Question)
  92. ## Question [/questions/{question_id}] + Attributes + question: `Favourite programming language?`

    (string, required) + published_at: `2014-11-11T08:40:51.620Z` (string) - An ISO8601 date when the question was published + choices (array[Choice], required) - An array of Choice objects
  93. ### Delete a Question [DELETE] + Relation: delete + Response

    204
  94. ## Choice [/questions/{question_id}/choices/{choice_id}] + Attributes + choice: Swift (string, required)

    + votes: 0 (number, required)
  95. ### Vote on a Choice [POST] This action allows you

    to vote on a question's choice. + Relation: vote + Response 201
  96. Conclusion • Move&Faster • Write&Less&Code • Don't&Break&Things • Embrace&Change

  97. Move%Fast,%Break%Nothing

  98. the$hypermedia$project!/!Hyperdrive the$hypermedia$project!/!representor*swi- !apiaryio!/!polls'app apiaryio!/!polls'api

  99. Ques%ons?

  100. Feedback • fuller.li/slides • ,KyleFuller • kyle@fuller.li