Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Broken APIs Break Trust: Tips for Creating and ...

Broken APIs Break Trust: Tips for Creating and Updating APIs

For many of us, APIs and their client libraries are the face of our applications to the world. We need to innovate with new features, but breaking changes are toxic to customer trust.

In this session you will pick-up concrete design patterns that you can use and anti-patterns that you can recognize so that your service API or library can continue to grow without breaking the world. Using real APIs and open source libraries as a case study, we’ll look at actual examples of specific strategies that you can employ to keep your API backwards compatible without painting yourself into a corner.

Alex Wood

April 18, 2018
Tweet

More Decks by Alex Wood

Other Decks in Technology

Transcript

  1. WHO AM I? ▸ AWS SDK for Ruby ▸ Twitter:

    @alexwwood ▸ Please do tweet your comments and questions about the talk! ▸ Bad Jokes
  2. AGENDA ▸ Defining APIs and Backwards Compatibility ▸ Safe API

    Changes ▸ Constraints and Validation ▸ Exceptions ▸ Undocumented API Contracts ▸ Conclusion: Backwards Compatibility Rules
  3. "THERE IS A THEORY WHICH STATES THAT IF EVER ANYONE

    DISCOVERS EXACTLY WHAT THE UNIVERSE IS FOR AND WHY IT IS HERE, IT WILL INSTANTLY DISAPPEAR AND BE REPLACED BY SOMETHING EVEN MORE BIZARRE AND INEXPLICABLE. THERE IS ANOTHER THEORY WHICH STATES THAT THIS HAS ALREADY HAPPENED." Douglas Adams, "Hitchhiker's Guide to the Galaxy"
  4. ANATOMY OF A WEB API ▸ Resources: Things that an

    API manages. ▸ Shape: A structure with members. ▸ Input Shape: A top-level request shape passed to an operation. ▸ Output Shape: A top-level response shape returned from an operation. ▸ Error Shape: A structure that represents an error response from an operation. ▸ Member: Belongs to a shape, represents a property of the shape. ▸ Operation: Something exposed by the service that can be invoked. ▸ NOTE: Can all of this apply to libraries which expose an API but do not make calls to the internet? Absolutely.
  5. EVOLUTION OF CLIENTS RELATIVE TO APIS CLIENT V3 API V3

    CLIENT V2 API V2 CLIENT V1 API V1
  6. EVOLUTION OF CLIENTS RELATIVE TO APIS CLIENT V3 API V3

    CLIENT V2 API V2 CLIENT V1 API V1 CLIENT V4 API V4
  7. EVOLUTION OF CLIENTS RELATIVE TO APIS CLIENT V3 API V3

    CLIENT V2 API V2 CLIENT V1 API V1 CLIENT V4 API V4 CLIENT ∞ API ∞
  8. ANATOMY OF A WEB API Trip travelers [string array] description

    [string] flight [Flight] ... SHAPE MEMBERS
  9. ANATOMY OF A WEB API Trip travelers [string array] description

    [string] flight [Flight] ... SHAPE MEMBERS SPECIAL MEMBER
  10. ANATOMY OF A WEB API Trip travelers [string array] description

    [string] flight [Flight] ... Flight flight_number [string] airline [string] status [string] ...
  11. ANATOMY OF A WEB API ▸ GetTrip ▸ CreateTrip ▸

    UpdateTrip ▸ ListTrips ▸ DeleteTrip
  12. ANATOMY OF A WEB API ▸ GetTrip ▸ CreateTrip ▸

    UpdateTrip ▸ ListTrips ▸ DeleteTrip ▸ GET /trips/:id ▸ POST /trips ▸ PATCH /trips/:id ▸ GET /trips ▸ DELETE /trips/:id
  13. ANATOMY OF A WEB API ▸ GetTrip ▸ CreateTrip ▸

    UpdateTrip ▸ ListTrips ▸ DeleteTrip ▸ GET /trips/:id ▸ POST /trips ▸ PATCH /trips/:id ▸ GET /trips ▸ DELETE /trips/:id ▸ 'trips#show' ▸ 'trips#create' ▸ 'trips#update' ▸ 'trips#index' ▸ 'trips#destroy'
  14. ANATOMY OF A WEB API trip_client.get_trip(id: 123) GET /trips/123 {

    trip: { travelers: ["Alex"], description: "RailsConf 2018", flight: { flight_number: "DL3378", airline: "Delta", status: "Landed" } } }
  15. ANATOMY OF A WEB API trip_client.get_trip(id: 123) GET /trips/123 {

    trip: { travelers: ["Alex"], description: "RailsConf 2018", flight: { flight_number: "DL3378", airline: "Delta", status: "Landed" } } }
  16. ANATOMY OF A WEB API trip_client.get_trip(id: 123) GET /trips/123 {

    trip: { travelers: ["Alex"], description: "RailsConf 2018", flight: { flight_number: "DL3378", airline: "Delta", status: "Delayed" # Because of course it was. } } }
  17. ANATOMY OF A WEB API ▸ Resources: Things that an

    API manages. ▸ Shape: A structure with members. ▸ Input Shape: A top-level request shape passed to an operation. ▸ Output Shape: A top-level response shape returned from an operation. ▸ Error Shape: A structure that represents an error response from an operation. ▸ Member: Belongs to a shape, represents a property of the shape. ▸ Operation: Something exposed by the service that can be invoked. ▸ NOTE: Can all of this apply to libraries which expose an API but do not make calls to the internet? Absolutely.
  18. ADDING MEMBERS - NEW CLIENT trip_client = TripClient.new resp =

    trip_client.get_trip(id: 321) resp.travelers # => ["Alex"] resp.confirmed # => true
  19. ADDING MEMBERS - OLD CLIENT trip_client = TripClient.new resp =

    trip_client.get_trip(id: 321) resp.travelers # => ["Alex"]
  20. INPUT SHAPES - ADDING AND REMOVING trip_client = TripClient.new resp

    = trip_client.list_trips # TripClient::Errors::MissingRequiredParameter
  21. INPUT SHAPES - ADDING AND REMOVING trip_client = TripClient.new resp

    = trip_client.list_trips # TripClient::Errors::MissingRequiredParameter WRONG
  22. INPUT SHAPES - ADDING AND REMOVING trip_client = TripClient.new resp

    = trip_client.list_trips # ArgumentError: missing required parameter # :confirmed
  23. INPUT SHAPES - ADDING AND REMOVING trip_client = TripClient.new resp

    = trip_client.list_trips # ArgumentError: missing required parameter # :confirmed WRONG
  24. KEY TAKEAWAYS ‣ Having your code break on you is

    a jarring, painful experience. Make serious efforts to avoid doing this to your users. ‣ Since some changes are harder than others, think ahead when initially launching your API to avoid the likelihood of needing to make "bad changes". ‣ For Web APIs, there is no complete way around this no matter how clever you are with clients. ‣ Community clients will be built. ‣ People will make raw, well-formed HTTP requests outside of your client libraries. ‣ Ruby does NOT get a free lunch.
  25. DID YOU HAVE TO DO THIS? I WAS THINKING THAT

    YOU COULD BE TRUSTED. Taylor Swift
  26. AGAIN: NO ADDING NEW REQUIRED PARAMETERS ▸ Breaks existing code

    AND old clients. ▸ Removing "required" attribute from parameters is okay.
  27. TERMINAL STATES ▸ Flight.status ▸ Interim: OnTime, Delayed, Boarding, InFlight

    ▸ Terminal: Landed, LandedOnTime, LandedLate, Cancelled
  28. EXCEPTIONS begin resp = trip_client.get_trip(id: id) rescue ResourceNotFound => e

    logger.info("Trip #{id} does not exist.") nil end # Suddenly this raises ResourceDeleted...
  29. EXCEPTIONS begin resp = trip_client.get_trip(id: id) rescue ResourceNotFound => e

    logger.info("Trip #{id} does not exist.") nil end # Suddenly this raises ResourceDeleted... WRONG
  30. EXCEPTIONS begin resp = trip_client.get_trip(id: id) rescue ResourceNotFound => e

    if e.previously_existed? logger.info("Trip #{id} previously deleted.") else logger.info("Trip #{id} does not exist.") end nil end
  31. WITH A SUFFICIENT NUMBER OF USERS OF AN API, IT

    DOES NOT MATTER WHAT YOU PROMISE IN THE CONTRACT, ALL OBSERVABLE BEHAVIORS OF YOUR SYSTEM WILL BE DEPENDED ON BY SOMEBODY. Hyrum's Law
  32. RELATED TALKS ▸ AWS re:Invent 2017: Embracing Change without Breaking

    the World (DEV319) ▸ AWS re:Invent 2017: Deploying and Managing Ruby Applications on AWS (DEV207) ▸ RailsConf 2018: The GraphQL Way: A new path for JSON APIs. ▸ Note: This talk already happened, but you can view later on YouTube for a different POV on some of these topics.
  33. FOUR OUT OF FIVE DENTISTS RECOMMEND SAVING THIS SLIDE FOR

    REFERENCE DO ▸ APIs ▸ Add members/shapes ▸ Add intermediate workflow states ▸ Add detail to exceptions ▸ Add new opt-in exceptions ▸ Loosening constraints ▸ Clients ▸ Forward compatible (support all of the above) ▸ Focus on discoverability ▸ APIs ▸ Remove/rename members and shapes. ▸ Change member types ▸ Add new terminal workflow states ▸ Add new exceptions without opt-in ▸ Tighten constraints ▸ Clients ▸ Validate API constraints DO NOT