BROKEN APIS BREAK TRUST
TIPS FOR CREATING AND UPDATING APIS
Slide 2
Slide 2 text
No content
Slide 3
Slide 3 text
Instagram: @pug_katsu
Slide 4
Slide 4 text
WHO AM I?
▸ AWS SDK for Ruby
▸ Twitter: @alexwwood
▸ Please do tweet your comments
and questions about the talk!
▸ Bad Jokes
Slide 5
Slide 5 text
AGENDA
▸ Defining APIs and Backwards Compatibility
▸ Safe API Changes
▸ Constraints and Validation
▸ Exceptions
▸ Undocumented API Contracts
▸ Conclusion: Backwards Compatibility Rules
Slide 6
Slide 6 text
DEFINITIONS
Slide 7
Slide 7 text
"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"
Slide 8
Slide 8 text
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.
Slide 9
Slide 9 text
EVOLUTION OF CLIENTS RELATIVE TO APIS
CLIENT
API
Slide 10
Slide 10 text
EVOLUTION OF CLIENTS RELATIVE TO APIS
CLIENT V2
API V2
CLIENT V1
API V1
Slide 11
Slide 11 text
EVOLUTION OF CLIENTS RELATIVE TO APIS
CLIENT V2
API V2
CLIENT V1
API V1
Slide 12
Slide 12 text
EVOLUTION OF CLIENTS RELATIVE TO APIS
CLIENT V3
API V3
CLIENT V2
API V2
CLIENT V1
API V1
Slide 13
Slide 13 text
EVOLUTION OF CLIENTS RELATIVE TO APIS
CLIENT V3
API V3
CLIENT V2
API V2
CLIENT V1
API V1
CLIENT V4
API V4
Slide 14
Slide 14 text
EVOLUTION OF CLIENTS RELATIVE TO APIS
CLIENT V3
API V3
CLIENT V2
API V2
CLIENT V1
API V1
CLIENT V4
API V4
CLIENT ∞
API ∞
Slide 15
Slide 15 text
EVOLUTION OF CLIENTS RELATIVE TO APIS
API'
CLIENT CLIENT'
Slide 16
Slide 16 text
API Backward Compatibility
+
Client Forward Compatibility
=
Slide 17
Slide 17 text
ANATOMY OF A WEB API
Trip
travelers [string array]
description [string]
flight [Flight]
...
Slide 18
Slide 18 text
ANATOMY OF A WEB API
Trip
travelers [string array]
description [string]
flight [Flight]
...
SHAPE
MEMBERS
Slide 19
Slide 19 text
ANATOMY OF A WEB API
Trip
travelers [string array]
description [string]
flight [Flight]
...
SHAPE
MEMBERS
SPECIAL
MEMBER
Slide 20
Slide 20 text
ANATOMY OF A WEB API
Trip
travelers [string array]
description [string]
flight [Flight]
...
Flight
flight_number [string]
airline [string]
status [string]
...
Slide 21
Slide 21 text
ANATOMY OF A WEB API
▸ GetTrip
▸ CreateTrip
▸ UpdateTrip
▸ ListTrips
▸ DeleteTrip
Slide 22
Slide 22 text
ANATOMY OF A WEB API
▸ GetTrip
▸ CreateTrip
▸ UpdateTrip
▸ ListTrips
▸ DeleteTrip
▸ GET /trips/:id
▸ POST /trips
▸ PATCH /trips/:id
▸ GET /trips
▸ DELETE /trips/:id
Slide 23
Slide 23 text
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'
Slide 24
Slide 24 text
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"
}
}
}
Slide 25
Slide 25 text
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"
}
}
}
Slide 26
Slide 26 text
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.
}
}
}
Slide 27
Slide 27 text
ANATOMY OF A WEB API
trip_client.get_trip(id: 321)
GET /trips/321
ResourceNotFound
Slide 28
Slide 28 text
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.
Slide 29
Slide 29 text
SAFE API CHANGES
Slide 30
Slide 30 text
I MIGHT DROP THE WORLD
...IF I CHANGE HANDS.
Lil' Wayne
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.
Slide 53
Slide 53 text
CONSTRAINTS,
VALIDATION,
AND EXCEPTIONS
Slide 54
Slide 54 text
DID YOU HAVE TO DO THIS?
I WAS THINKING THAT YOU
COULD BE TRUSTED.
Taylor Swift
Slide 55
Slide 55 text
AGAIN: NO ADDING NEW REQUIRED PARAMETERS
▸ Breaks existing code AND old clients.
▸ Removing "required" attribute from parameters is okay.
EXCEPTIONS
trip_client.get_trip(id: 321)
GET /trips/321
ResourceNotFound
Slide 68
Slide 68 text
EXCEPTIONS
begin
resp = trip_client.get_trip(id: id)
rescue ResourceNotFound => e
logger.info("Trip #{id} does not exist.")
nil
end
Slide 69
Slide 69 text
EXCEPTIONS
trip_client.get_trip(id: 321)
GET /trips/321
ResourceNotFound || ResourceDeleted
Slide 70
Slide 70 text
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...
Slide 71
Slide 71 text
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
Slide 72
Slide 72 text
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
Slide 73
Slide 73 text
UNDOCUMENTED
API CONTRACTS
Slide 74
Slide 74 text
WHEN GIVEN A CHOICE
BETWEEN TWO OPTIONS,
CHOOSE ALL THREE.
John Carlyle
Slide 75
Slide 75 text
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
Slide 76
Slide 76 text
CONCLUSION
API DESIGN RULES YOU CAN USE!
Slide 77
Slide 77 text
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.
Slide 78
Slide 78 text
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