heavybit presents: stripe's api lead amber feng on api design
DESCRIPTION
Heavybit member and payment processing company Stripe's manages 106 endpoints, 65 versions and 6 API clients all without breaking things. This presentation was given by Stripe's API lead Amber Feng on designing APIs for engagement and ease-of-use.TRANSCRIPT
MOVE FAST,DON'T BREAK YOUR API
AMBER FENG @amfeng
LET'S BUILDAN API!
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
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
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
curl https://stripe.com/v1/charges -u API_KEY -d card_number=4242424242424242 -d amount=100
=>
{ id: "ch_xxx", amount: 100, card_number: "*4242", success: true }
WHAT NEXT?
WHAT NEXT?MORE ENDPOINTS
WHAT NEXT?MORE ENDPOINTSMORE FUNCTIONALITY
WHAT NEXT?MORE ENDPOINTSMORE FUNCTIONALITYMORE CHANGES
WHAT NEXT?MORE ENDPOINTSMORE FUNCTIONALITYMORE CHANGESMORE PROBLEMS
LARGE, TANGLEDCODEBASE
COPY-PASTA
ERROR-PRONEDEPENDENCIES
"CRAP, I FORGOT TO UPDATE THE DOCS!"
— Everyone ever
HOW DO WEMAKE IT BETTER?
DESIGN FORYOURSELF, TOO
SEPARATE DIFFERENT LAYERS OF LOGIC
AuthenticationValidationEndpoint-specific logicConstructing the responseError handling
Error handler
Authenticator
API logic
use ErrorHandleruse Authenticator
get '/v1/charges/:id' do user = env.user id = params[:id] unless user.get_charge(id) raise UserError.new("No charge #{id}!") endend
APIMethods &APIResources
class ChargeCreateMethod < AbstractAPIMethod required :amount, :integer required :card_number, :string
resource ChargeAPIResource def execute create_charge(amount, card_number) endend
class ChargeAPIResource < AbstractAPIResource required :id, :string required :amount, :integer required :card_number, :string required :success, :boolean
def describe_card_number charge.redacted_card_number endend
post '/v1/charges' do APIMethod::ChargeCreateMethod.invokeend
get '/v1/charges' do APIMethod::ChargeRetrieveMethod.invokeend
MAKE IT HARD TOMESS UP
class ChargeCreateMethod < AbstractAPIMethod required :amount, :integer required :card_number, :string
document :amount, "Amount, in cents." document :card_number, "The card number."
...end
HIDE BACKWARDS COMPATIBILITY
<todo: tweet>
def execute if !user.version_1? && params[:amount] raise UserError.new("Invalid param.") end
...
if !user.version_1? response.delete(:amount) endend
GATES
- :version: 2014-09-24 :new_gates: - :gate: allows_amount :description: >- Sending amount is now deprecated.
def execute if !user.gating(:allows_amount) && params[:amount] raise UserError.new("Invalid param.") end
...
if !user.gating(:allows_amount) response.delete(:amount) endend
COMPATIBILITYLAYERS
Request compatibility
API logic
Construct API response
Response compatibility
IN THEREAL WORLD
106 ENDPOINTS65 VERSIONS6 API CLIENTS
DESIGN FOR YOURSELF:SEPARATE LAYERS OF LOGICMAKE IT HARD TO MESS UPHIDE BACKWARDS COMPAT
WHAT ELSE?
NOT SURE.(WE'RE STILL FIGURING IT OUTAS WE GO.)
THANKS! (:@amfeng