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

Move fast, don't break your API

8727f7875a079ff297d084a03ef1356e?s=47 Amber Feng
September 30, 2014

Move fast, don't break your API

8727f7875a079ff297d084a03ef1356e?s=128

Amber Feng

September 30, 2014
Tweet

Transcript

  1. MOVE FAST, DON'T BREAK YOUR API AMBER FENG @amfeng

  2. None
  3. LET'S BUILD AN API!

  4. 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
  5. 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
  6. 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
  7. 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 }
  8. None
  9. WHAT NEXT?

  10. WHAT NEXT? MORE ENDPOINTS

  11. WHAT NEXT? MORE ENDPOINTS MORE FUNCTIONALITY

  12. WHAT NEXT? MORE ENDPOINTS MORE FUNCTIONALITY MORE CHANGES

  13. WHAT NEXT? MORE ENDPOINTS MORE FUNCTIONALITY MORE CHANGES MORE PROBLEMS

  14. LARGE, TANGLED CODEBASE

  15. COPY-PASTA

  16. ERROR-PRONE DEPENDENCIES

  17. "CRAP, I FORGOT TO UPDATE THE DOCS!" — Everyone ever

  18. HOW DO WE MAKE IT BETTER?

  19. DESIGN FOR YOURSELF, TOO

  20. SEPARATE DIFFERENT LAYERS OF LOGIC

  21. Authentication Validation Endpoint-specific logic Constructing the response Error handling

  22. Error handler Authenticator API logic

  23. 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
  24. APIMethods & APIResources

  25. class ChargeCreateMethod < AbstractAPIMethod required :amount, :integer required :card_number, :string

    resource ChargeAPIResource def execute create_charge(amount, card_number) end end
  26. 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
  27. post '/v1/charges' do APIMethod::ChargeCreateMethod.invoke end get '/v1/charges' do APIMethod::ChargeRetrieveMethod.invoke end

  28. MAKE IT HARD TO MESS UP

  29. class ChargeCreateMethod < AbstractAPIMethod required :amount, :integer required :card_number, :string

    document :amount, "Amount, in cents." document :card_number, "The card number." ... end
  30. HIDE BACKWARDS COMPATIBILITY

  31. <todo: tweet>

  32. def execute if !user.version_1? && params[:amount] raise UserError.new("Invalid param.") end

    ... if !user.version_1? response.delete(:amount) end end
  33. GATES

  34. - :version: 2014-09-24 :new_gates: - :gate: allows_amount :description: >- Sending

    amount is now deprecated.
  35. 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
  36. COMPATIBILITY LAYERS

  37. Request compatibility API logic Construct API response Response compatibility

  38. IN THE REAL WORLD

  39. 106 ENDPOINTS 65 VERSIONS 6 API CLIENTS

  40. DESIGN FOR YOURSELF: SEPARATE LAYERS OF LOGIC MAKE IT HARD

    TO MESS UP HIDE BACKWARDS COMPAT
  41. WHAT ELSE?

  42. NOT SURE. (WE'RE STILL FIGURING IT OUT AS WE GO.)

  43. THANKS! (: @amfeng