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.