Abstractions are hard. They may bleed underlying complexity. They can
compound the nastiness they were built to sequester. They might ineffectively
guide the developer as she formulates how to model her world in code. The
expert engineer often thinks to herself a few keystrokes away from another
install, “this may be way more trouble than it’s worth.”
And so maybe against better judgment, we tread into the world of abstraction building by creating a set of client libraries for our API.
This post aims to highlight a few design decisions that we think make the technical economics of adding yet another dependency, yet another potential point-of-failure, work out. While we’ve also open-sourced our ruby and python implementations, here we’ll focus on our node client.
Just The Pipes
Button’s bread and butter is connecting apps in meaningful ways. Our goal is to present the useful services of one app inside another. A key part of our ecosystem then is knowing when a user takes advantage of this connection and buys something in a different app than they started. This is generally accomplished by the app offering the service telling us an order occurred via our HTTP Orders API.
If I’m sitting down to report orders back to Button, there’s going to be a
whole litany of things I can just not be bothered to do. I’m probably not
going to build a thorough class hierarchy for an
Order. I’m probably not
going to do a whole lot of payload validation, i.e. checking that my dates are
compliant. After all,
HTTP 400s exist for a reason. I’m probably not going
to pull in an awful lot of third-party dependencies.
What I am going to do is bury a bunch of HTTP boilerplate (things like HTTP
verbs, Basic Auth, payload unmarshalling, …) up inside a few handy functions
deleteOrder. I am going make sure
they expose as clean and congruent an interface to my application code as
possible. They’ll return platform-idiomatic types (objects, in the case of
node) and match the asynchronous customs of the surrounding code (be it node-
style callbacks, promises, or otherwise).
What this ends up looking like is just the pipes, just the ability to conveniently express intent over a wire. Because our partners would all at a bare minimum have to implement these pipes themselves, we see it as a worthy opportunity to provide abstraction.
Those inclined for something more heavyweight are of course invited to build on top.
These ideas are incarnated in a simple interface. To abstract authentication and network calls, create an object bound to an organization’s API key:
Fetching a resource is then as simple as grabbing the
orders key and calling
get on it:
To be compatible with Promises, we have an interface that accepts your own promise implementation for use in all API calls:
This keeps us dependency-free and keeps you from having to cast promises to your preferred implementation.
Defining our clients as simple communicators keeps bytes down and transitive dependencies out. Zero transitive dependencies in turn lets us target a wide range of versions for each supported platform and stay fully in control of and accountable for the code that we ship. It means we can hide the boring boilerplate and annoying gotchas of talking to a server away while also not obligating you into a more abstruse or rigid interface than is absolutely required. So shoutout to our homies pybutton.client, Button::Client, and button-client-node.
We hope you’ll
pip install pybutton,
npm install @button/button-client-node, and
gem install button with confidence.