Skip to content

API Client conventions

Rafael Sales edited this page Aug 27, 2021 · 2 revisions

Structure

Client class

A client class is one which methods map to actions in the API.

E.g. AirbnbAPI::ReservationClient.new(auth).list

Response class

A response class is one that translates low level API attribute names and formatting to the naming and format used in the app.

E.g. 1: The Airbnb reservation response has an attribute named guest_alias_email, but we store that same attribute as guest_email in our tables/model, so the response object should respond to reservation_response.guest_email

E.g. 2: The Airbnb reservation response has two attributes: listing.check_in_time and start_date, but we use the combination of both with name check_in_at throughout the app, so the response object should respond to reservation_response.check_in_at

Best Practices

  • API Clients should not have any app dependencies - think of an API client as a gem, that shouldn't depend on classes of the app consuming it. E.g. An API client shouldn't make calls to ActiveRecord models or other services.
  • Response objects should have method names that match our app naming.

Code Example

# Base path: app/services
module AirbnbAPI
  class Client
    include HTTParty
  end

  # If one client class gets too messy, extract request methods grouped by resource:
  class AccountClient < Client
    def reservations
      response = make_request
      response.map { |reservation_data| ReservationResponse.new(reservation_data) }
    end
  end

  # Response objects should provide accessors that wrap the ugliness of the API
  class BaseResponse
    # Common logic between response objects, if any

    attr_reader :data

    def initializer(data)
      @data = data
    end
  end

  class ReservationResponse < BaseResponse
    def starts_at
      Time.parse(data.fetch('FuNkY_StArTs_At'))
    end

    # If you need to save it into an AR as is, the `to_attributes` should map
    # the API attributes to the AR attributes.
    # If you need to manipulate the to_attributes in order to use when assigning
    # to your AR object, it's probably better to just call each instance method
    # directly in the caller.
    def to_attributes
      {
         starts_at: starts_at,
         ...
      }
    end
  end

  class WebhookHandler
    attr_reader :request, :params

    def initialize(request:, params:)
      @request = request
      @params = params
    end
  end
end
Clone this wiki locally