Skip to content

Instantly share code, notes, and snippets.

@nateklaiber
Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nateklaiber/53c918b468ed1921a33b to your computer and use it in GitHub Desktop.
Save nateklaiber/53c918b468ed1921a33b to your computer and use it in GitHub Desktop.
Envelope Expansion Example

I'll use Stripe as an example. The client is responsible for:

  • Configuration
  • Routing
  • Connection Handling
  • Request Handling
    • Raw requests with JSON
    • Object model requests that wrap the raw request (allow us to say Stripe::Request::Charge.get(id: 'sdfsdf'))
  • Object Creation. This includes top level and associations.
# Configuration and setup
configuration = Stripe::Configuration.new(config_params_or_block)

# Find the route I want to make the request against
routes              = Stripe::Client.routes  
charge_detail_route = routes.route_for('charge-detail')
charge_detail_url   = charge_detail.url_for(id: 'ch_1047M22eZvKYlo2CzsVZ0cX8')

# Make a connection to the API
connection = Stripe::Connection.new(configuration)

# Make a request to the API. This is normally wrapped in a specific `Request` model.
request_handler = Stripe::RequestHandler.new(connection)
request         = request.get(charge_detail_url)

# Retrieve the response body as JSON
response = request.body

# Wrap in an object model
charge = Stripe::Model::Charge.new(response)

# Now we can interact with the model and it's attributes...

charge.created
# => 1401284558

charge.paid
# => true

# Now, the `customer` is expandable. If it exists, then it's an `ID` by default. If expanded, then it's the full representation.

# Without expansion.
charge.customer
# => 'cus_47Ltj7MTZguSEN'

# With expansion. We wrap it in the `Customer` model.
charge.customer
# => Stripe::Model::Customer

With that last part, for the sake of consistency, I want to always return a Stripe::Model::Customer object. This means the customer method needs to know if it's expanded or not. If not, then it doesn't need to make an extra request. If only the ID is provided, it needs to lazy load the Customer via another API call.

This way, a consumer of the client can safely call:

charge.customer.email
charge.customer.account_balance

They don't have to determine, from the outer scope, if they need to make a second request based on the value of customer.

This is why I'd prefer a method of customer_id and customer as different envelopes. Stripe could have returned the nested object:

{
  "customer": {
    "id": "cus_47Ltj7MTZguSEN"
  }
}

However, I am still left to inspect the object to determine if we have the full object or only the ID.

I am also open to there being better approaches. When I build API clients, I like to keep a clean separation of responsibilities without intermixing different aspects.

Any feedback would be welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment