MOVE FAST,
DON'T BREAK YOUR API
AMBER FENG @amfeng
Slide 2
Slide 2 text
No content
Slide 3
Slide 3 text
LET'S BUILD
AN API!
Slide 4
Slide 4 text
post '/v1/charges' do
card_number = params[:card_number]
amount = params[:amount]
charge = create_charge(card_number, amount)
json {
id: charge.id,
amount: charge.amount
card_number: charge.redacted_card_number,
success: charge.success
}
end
Slide 5
Slide 5 text
post '/v1/charges' do
...
unless card_number.length == 16
return {error: "Invalid card number."}
end
unless amount > 0 and amount <= CHARGE_MAX
return {error: "Invalid amount."}
end
...
end
Slide 6
Slide 6 text
post '/v1/charges' do
api_key = get_api_key
user = User.find_by_key(api_key)
unless user
return {error: "Invalid API key."}
end
...
end
WHAT NEXT?
MORE ENDPOINTS
MORE FUNCTIONALITY
MORE CHANGES
Slide 13
Slide 13 text
WHAT NEXT?
MORE ENDPOINTS
MORE FUNCTIONALITY
MORE CHANGES
MORE PROBLEMS
Slide 14
Slide 14 text
LARGE, TANGLED
CODEBASE
Slide 15
Slide 15 text
COPY-PASTA
Slide 16
Slide 16 text
ERROR-PRONE
DEPENDENCIES
Slide 17
Slide 17 text
"CRAP, I FORGOT
TO UPDATE THE DOCS!"
— Everyone ever
Slide 18
Slide 18 text
HOW DO WE
MAKE IT BETTER?
Slide 19
Slide 19 text
DESIGN FOR
YOURSELF, TOO
Slide 20
Slide 20 text
SEPARATE DIFFERENT
LAYERS OF LOGIC
Slide 21
Slide 21 text
Authentication
Validation
Endpoint-specific logic
Constructing the response
Error handling
Slide 22
Slide 22 text
Error handler
Authenticator
API logic
Slide 23
Slide 23 text
use ErrorHandler
use Authenticator
get '/v1/charges/:id' do
user = env.user
id = params[:id]
unless user.get_charge(id)
raise UserError.new("No charge #{id}!")
end
end
Slide 24
Slide 24 text
APIMethods &
APIResources
Slide 25
Slide 25 text
class ChargeCreateMethod < AbstractAPIMethod
required :amount, :integer
required :card_number, :string
resource ChargeAPIResource
def execute
create_charge(amount, card_number)
end
end
Slide 26
Slide 26 text
class ChargeAPIResource < AbstractAPIResource
required :id, :string
required :amount, :integer
required :card_number, :string
required :success, :boolean
def describe_card_number
charge.redacted_card_number
end
end
Slide 27
Slide 27 text
post '/v1/charges' do
APIMethod::ChargeCreateMethod.invoke
end
get '/v1/charges' do
APIMethod::ChargeRetrieveMethod.invoke
end
Slide 28
Slide 28 text
MAKE IT HARD TO
MESS UP
Slide 29
Slide 29 text
class ChargeCreateMethod < AbstractAPIMethod
required :amount, :integer
required :card_number, :string
document :amount, "Amount, in cents."
document :card_number, "The card number."
...
end
Slide 30
Slide 30 text
HIDE BACKWARDS
COMPATIBILITY
Slide 31
Slide 31 text
Slide 32
Slide 32 text
def execute
if !user.version_1? && params[:amount]
raise UserError.new("Invalid param.")
end
...
if !user.version_1?
response.delete(:amount)
end
end
Slide 33
Slide 33 text
GATES
Slide 34
Slide 34 text
-
:version: 2014-09-24
:new_gates:
-
:gate: allows_amount
:description: >-
Sending amount is now deprecated.
Slide 35
Slide 35 text
def execute
if !user.gating(:allows_amount) &&
params[:amount]
raise UserError.new("Invalid param.")
end
...
if !user.gating(:allows_amount)
response.delete(:amount)
end
end
Slide 36
Slide 36 text
COMPATIBILITY
LAYERS
Slide 37
Slide 37 text
Request compatibility
API logic
Construct API response
Response compatibility
Slide 38
Slide 38 text
IN THE
REAL WORLD
Slide 39
Slide 39 text
106 ENDPOINTS
65 VERSIONS
6 API CLIENTS
Slide 40
Slide 40 text
DESIGN FOR
YOURSELF:
SEPARATE LAYERS OF LOGIC
MAKE IT HARD TO MESS UP
HIDE BACKWARDS COMPAT