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

Move fast, don't break your API

Amber Feng
September 30, 2014

Move fast, don't break your API

Amber Feng

September 30, 2014
Tweet

More Decks by Amber Feng

Other Decks in Programming

Transcript

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

    View Slide

  2. View Slide

  3. LET'S BUILD
    AN API!

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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
    }

    View Slide

  8. View Slide

  9. WHAT NEXT?

    View Slide

  10. WHAT NEXT?
    MORE ENDPOINTS

    View Slide

  11. WHAT NEXT?
    MORE ENDPOINTS
    MORE FUNCTIONALITY

    View Slide

  12. WHAT NEXT?
    MORE ENDPOINTS
    MORE FUNCTIONALITY
    MORE CHANGES

    View Slide

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

    View Slide

  14. LARGE, TANGLED
    CODEBASE

    View Slide

  15. COPY-PASTA

    View Slide

  16. ERROR-PRONE
    DEPENDENCIES

    View Slide

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

    View Slide

  18. HOW DO WE
    MAKE IT BETTER?

    View Slide

  19. DESIGN FOR
    YOURSELF, TOO

    View Slide

  20. SEPARATE DIFFERENT
    LAYERS OF LOGIC

    View Slide

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

    View Slide

  22. Error handler
    Authenticator
    API logic

    View Slide

  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

    View Slide

  24. APIMethods &
    APIResources

    View Slide

  25. class ChargeCreateMethod < AbstractAPIMethod
    required :amount, :integer
    required :card_number, :string
    resource ChargeAPIResource
    def execute
    create_charge(amount, card_number)
    end
    end

    View Slide

  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

    View Slide

  27. post '/v1/charges' do
    APIMethod::ChargeCreateMethod.invoke
    end
    get '/v1/charges' do
    APIMethod::ChargeRetrieveMethod.invoke
    end

    View Slide

  28. MAKE IT HARD TO
    MESS UP

    View Slide

  29. class ChargeCreateMethod < AbstractAPIMethod
    required :amount, :integer
    required :card_number, :string
    document :amount, "Amount, in cents."
    document :card_number, "The card number."
    ...
    end

    View Slide

  30. HIDE BACKWARDS
    COMPATIBILITY

    View Slide


  31. View Slide

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

    View Slide

  33. GATES

    View Slide

  34. -
    :version: 2014-09-24
    :new_gates:
    -
    :gate: allows_amount
    :description: >-
    Sending amount is now deprecated.

    View Slide

  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

    View Slide

  36. COMPATIBILITY
    LAYERS

    View Slide

  37. Request compatibility
    API logic
    Construct API response
    Response compatibility

    View Slide

  38. IN THE
    REAL WORLD

    View Slide

  39. 106 ENDPOINTS
    65 VERSIONS
    6 API CLIENTS

    View Slide

  40. DESIGN FOR
    YOURSELF:
    SEPARATE LAYERS OF LOGIC
    MAKE IT HARD TO MESS UP
    HIDE BACKWARDS COMPAT

    View Slide

  41. WHAT ELSE?

    View Slide

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

    View Slide

  43. THANKS! (:
    @amfeng

    View Slide