October 10, 2014

Move fast, don't break your API

As a sequel to my talk last year on building Stripe's API, I thought it'd be useful to go over how we scaled some of our internal abstractions to continue building and iterating quickly. I gave this talk at APIStrat Chicago a couple of weeks ago and several events at HeavyBit last week, who generously recorded and transcribed the whole thing for anyone who'd like to watch a video. (Thanks, HeavyBit!)

I'm the kind of person who will impatiently watch videos of talks or lectures on 2x speed and greatly prefer reading a blog post that can be skimmed easily, so I decided to write an accompanying post to go with the slides.

Enjoy! (:

Let's build an API!

Stripe has a lot of APIs, and as a result we had to quickly figure out how to scale our abstractions and code. Since code is worth a thousand words (that's a saying, right?), I'll run through an example of building an API.

Here's a super simple example of an API endpoint in Sinatra that creates credit card charges:

post '/charges' do
  # Authentication
  api_key = get_api_key
  user = User.find_by_key(api_key)

  unless user
    return {error: "Invalid API key."}
  end

  # Collect user parameters
  card_number = params[:card_number]
  amount = params[:amount].to_i

  # Validations
  unless card_number.length == 16
    return {error: "Invalid card number."}
  end

  unless amount > 0 and amount <= CHARGE_MAX
    return {error: "Invalid amount."}
  end

  # Actually create the charge
  charge = create_charge(card_number, amount)

  # Return an API response
  json {
    id: charge.id,
    amount: charge.amount
    card_number: charge.redacted_card_number,
    success: charge.success
  }
end

It's starting to get a little crowded in here, but it's more or less a working API. That was easy!™

What next?

As you start adding new endpoints, functionality, and changes you run into more problems.

How do you (scalably) do things like authentication, validation, actual API logic, error handling, authentication, and at the same time support every combination of behaviors that has ever existed in the past? We can't ever break integrations—particularly as a payments processor, broken integrations means our users are literally losing money every minute. We need to be able to build and change things rapidly without compromising any API stability or backwards compatibility.

Once your API has reached a certain size, something that also starts to creep up on you is dependencies. Documentation is a good example of this. Say you build an API and write up docs in the form of static HTML or markdown. It launches and everyone is happy.

A week later you decide to add something to the API. You diligently add it to your API code and maybe even remember to make the change in the docs as well.

With more additions or updates though, sooner or later this is going to happen:

Crap, I forgot to update the docs! — Everyone, ever

Sound familiar? (This has definitely happened to me more than once.) How did we start making these problems less painful?

Separate the layers of responsibility

In the example above, many things are going on at once: authentication, validation, endpoint-specific logic, error handling, and constructing the response.

Separation of responsibilities is CS 101, so we started moving things out and building abstractions around them.

For authentication and error handling, we use Rack middleware and it works pretty well. You shouldn't have to worry about authenticating users in the middle of your API logic (most frameworks have a concept of before filters for this as well). You also shouldn't have to hardcode error response formats. Wouldn't it be nice if you could just throw an error and know that someone else will catch it to format it into the proper response later?

use ErrorHandler
use Authenticator

get '/charges/:id' do
  user = env.user
  id = params[:id]

  if charge = user.get_charge(id)
    json {
      id: charge.id
      ...
    }
  else user.get_charge(id)
    raise UserError.new("No charge #{id}!")
  end
end

We represent endpoint validations, logic, and response generation internally in our code as APIMethods and APIResources. If you're familiar with MVC, they're very similar to controllers and views for your API.

class ChargeCreateMethod < AbstractAPIMethod
  required :amount, :integer
  required :card_number, :string

  resource ChargeAPIResource

  def execute
    create_charge(amount, card_number)
  end
end

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

Make it really hard to mess up

A good UX design principle is that you should make it really hard for your users to mess up or do the wrong thing. Why not apply this toward building the API as well?

One thing that we did that I thought was really cool was a system for documenting our API. To try to address "I forgot to update the docs!" syndrome, we made it really hard to forget by putting the documentation right under the code that adds a new property.

class ChargeCreateMethod < AbstractAPIMethod
  required :amount, :integer
  required :card_number, :string

  document :amount, "Amount, in cents."
  document :card_number, "The card number."

  ...
end

Our documentation then auto-generates itself from these specs—for changing most things, there's no need to go dig up static HTML files.

Similarly for our API libraries (or at least those that can support it), we don't hardcode properties for each object but instead dynamically generate them based on the properties present in the response that is received. This way we don't have to worry about adding new fields to object definitions in each library.

Hide your backwards compatibility

We're often asked how we implement our API backwards compatibility. This probably merits an entire talk and post by itself, but I'll go over it at a high level.

When a user starts implementing Stripe for the first time they don't need to worry about API versions. Instead it's invisible—they'll innocently make their first API request, we'll record what internal version they're on, and from then on our code takes care of making sure we never break their integration.

If a user wants to worry about versions they can: we allow overrides to be sent in via request headers and users can upgrade their version via the dashboard. However most people won't care, so they shouldn't have to know about it. All most Stripe users see is that their integration, even if they first wrote it years ago, never breaks.

All of our versions live in the same code base and are deployed to the same service. We don't do separate services or deploys for different versions. The downside of doing is that it's easy for things to get hairy after a while.

First pass

Imagine sometime down the road Stripe decides to deprecate the amount parameter and all charges are now $1. (Disclaimer: I'm pretty sure we will never actually do this.)

What's the naive way to implement that change in behavior? Maybe something like this:

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

  ...

  if !user.old_version?
    response.delete(:amount)
  end
end

There are a couple of problems with this. First, who knows what old_version? is supposed to mean? You can infer it from the code, but it's really not intuitive and is just waiting for accidental regressions. Second, the regular API logic and legacy logic are being mixed together. If someone wanted to add or update the something in the current API (somewhere in that ...), they would have to wade through those extra conditionals.

Gates

We've modeled our versioning system around a series of gates. A "gate" is a database flag (similar to feature flags) that our code can use to determine what functionality to allow. For example, if a user is on an old version and is therefore allowed to send the amount parameter, they're on the allows_amount gate.

We declare all of the versions along with the corresponding functionality gates in a single YAML file:

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

and are able to decouple versions from the actual behavior they represent in our code:

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

To go one step further and actually get that logic out of the endpoint execution code, separate compatibility layers were added to our code flow. Because who doesn't need more layers of indirection?

Now when a request comes in it first filters through the request compatibility. That layer may or may not reject it based on the parameters (like if someone passes in amount who isn't allowed to) or munge the parameters to something that the later logic expects.

After the response is created, it passes through another layer of response compatibility which transforms the response into whatever the user's version dictates. The great thing about this is that the API logic and response construction steps can represent the current version of the API without any legacy edge case clutter.

It's worth noting that these layers aren't free: there's a certain amount of complexity added when fiddling with requests and responses that are passed through, but we made the tradeoff to be able to keep our general API code (what engineers spend 80-90% of their time working on) clean and easier to reason about.

In the real world

What does this look like in practice? Stripe has (at the time of writing) 106 endpoints, 65 versions, and 6 API client libraries. You can do the combination math yourself.

We would be far behind where we are today if we couldn't find, read, and change code quickly without being afraid of breaking our users.

Conclusion

So design for yourself. Spend some effort not just thinking about how you can optimize your users' experiences, but how you can optimize your own as well.

Stripe's far from perfect; we're learning more and more every day and (like any other startup) there are still very many things in our code that annoy and embarass us. I hope this gives others a sense of things we've learned over the years and helps other developer companies who are tackling the same problems.

If you're ever interested in chatting about any of these topics, I'd love to chat—drop me an email or poke me on Twitter.

I ended up writing far more than I had intended so if you made it down this far, congratulations and thanks for reading!

Credit to Sheena Pakanati, Saikat Chakrabarti, Ross Boucher, Greg Brockman, and many others at Stripe for contemplating, building, and iterating on everything covered in this post.

May 8, 2014

Opening files in Github from Vim

I spend a lot of time nowadays doing code reviews and helping spin people up on Stripe (or more generally, distributing knowledge about our code base). One thing I found myself needing over and over again was the ability to show someone a file or some code over IM or Slack. Since we use Github to host our repositories, this is as easy as linking to the online file.

However, it's pretty annoying to have to use Github's UI to navigate through folders, and I almost always mess up trying to type the full path in the URL (blob? tree?). I also use Command-T with Vim, which means I never remember file paths or exact file names. (If you don't use Command-T or something similar, you should—it's the single biggest improvement to my code reading/writing productivity over the last couple of years.)

"Wouldn't it be awesome if I could just open a file in Github directly from Vim with some keyboard shortcut?", I thought to myself.

I did a bunch of research to try to figure out how hard it would be to build myself (I've never written a Vim plugin before), and stumbled upon git web--browse. From the man page:

NAME
   git-web--browse - git helper script to launch a web browser

SYNOPSIS
   git web--browse [OPTIONS] URL/FILE ...

DESCRIPTION
   This script tries, as much as possible, to display the URLs and FILEs
   that are passed as arguments, as HTML pages in new tabs on an already
   opened web browser.

Sounds like exactly what I want!

Amazingly, fugitive, a Git wrapper for Vim, actually has this functionality built in. If you have fugitive installed already, try it right now: go to a file that's hosted on Github, then type in the command :Gbrowse. It'll automagically open the corresponding link (keeping in mind what branch you're on, and everything) in your browser. You can even highlight a couple lines in visual-mode to have those be highlighted on Github.

I ended up remapping :Gbrowse in my .vimrc to something easier to type, you should too:

noremap <leader>w :Gbrowse<cr>

I use this at least a few times every single day. Hope you find it useful too!

September 20, 2013

Painting Adventures

A couple of friends and I decided around a month ago that we wanted to get better at drawing, so we decided to embark on one of those "draw one thing every day for 365 days" projects. Well, if we're being realistic, for approximately 365 days—we even named ourselves "Approximately 1095" (1095 = 365 x 3).

It's been really fun so far:

(I'll write up a separate post later about how/where I'm posting these!)

One day I woke up to discover that my friend Jack had, that morning (on mostly a whim), gone out and purchased a set of painting supplies and was happily painting away on an easel in front of the bay windows in his room.

I thought that was pretty amazing and was inspired to do the same. As much as I like drawing and sketching, the lack of color is sometimes a bit depressing. I've always wanted to experiment with painting (having done digital/Photoshop painting but never physical painting), and decided to try it out1!

Color mixing

The day my acrylic paints arrived (2-day Amazon Prime, ftw), I sat down and practiced mixing a bunch of colors to get a feel for the ratios and color combinations. And it's a good thing I did, because I instantly made the newbie mistake of squeezing out entirely too much dark paint (a dark pigment like red will overwhelm a lighter one like yellow; 90% yellow + 10% red is basically already dark orange).

I ended up with a bunch of index cards with color gradients, much like a Pantone catalog. Pretty fun. I actually like using physical paints a lot more than using a digital color picker, since the colors blend together pretty interestingly when painted, and aren't just one flat color.

First painting!

The prompt of the day for our drawing project was "mermaid" and I knew I wanted to paint a landscape (so hard to mess up!), so I decided to paint the underwater castle from the Little Mermaid.

The hardest part was probably being too scared to paint over things that I had already painted in the background (no undo or layers?!). It was really fun though.

Highly, highly recommend picking up painting as a hobby. It's relatively cheap (I got everything for around $50-60 on Amazon), and it's super fun and relaxing/therapeutic to be swirling paint around for a couple of hours. Not to mention you get an awesome physical piece of art that you can then decorate with, gift, or hide in your closet.

I decided to start off with acrylic because watercolor is really hard to control or recover from mistakes with, and oil has a messier cleanup, but it's really up to your own preference. Enjoy!

I hope to be posting more about the rest of my painting adventures soon! (:

1: I'm still continuing the drawing project, I'll just substitute paintings in every once in a while.

February 23, 2013

Building Stripe's API

I just got back from New York, where I gave my first conference talk ever at the API Strategy and Practice conference. Pretty exciting!

I thought it would be interesting to talk about Stripe's API, particularly lessons learned and what kind things we did to try to make using the API as easy as possible. I've included the slides below, but most of the content isn't on the slides so I'll try to cover some of the highlights below.

As a disclaimer, we definitely don't know everything. A lot of what you see on Stripe today is the product of thought and discussion, as well as a lot of trial and error. I hope you find something in here applicable toward your own API! (:

Highlights

Make it easy to get started

This sounds like a no-brainer, but the best way to get people to try out your API is to make it really easy to get started.

We do things like including passable code snippets throughout our site and documentation. For example, one of the first things you'll see on our front page is a curl snippet you can paste into a terminal to simulate charging a credit card. Regardless of whether you have a Stripe account or not (if logged in, we embed your test API key, else, it's a sample account's API key), you can see the Stripe API in action.

All of our documentation code snippets are similarly easy to copy and paste—we try to embed as much information as possible (API keys, actual object IDs from your account) so you don't have to.

Language-specific libraries and documentation

Since Stripe is an HTTP API, you could easily integrate it into your application with any HTTP client library. However, this still requires constructing requests and parsing responses on your own.

To make this easier, we support official open-source libraries in the most web today (turns out people are pretty attached to their favorite languages). There was a lot of internal discussion about whether we actually wanted to support our own API or allow the community to organically start and maintain the projects themselves.

Ultimately, I think there's a certain degree of trust that users put in official libraries, which makes it easy for them to get started (as opposed to trying to audit different third-party libraries). It also makes it really easy for us to have language-specific documentation this way.

Have a focused API, but allow flexibility

One thing that we found critically important was to keep the API focused. It's tempting to add new features that are nice, but not necessary.

For example, our users frequently want us to add better analytics, tax calculations, or to send user receipts. While these things are nice, they're not our core competency and may very well clutter our API with too many options1.

Instead, you should give your users the tools to be able to write their own extensions. We allow our users (and third party applications) to hook into Stripe in a couple of ways.

Webhooks

Webhooks are a way of Stripe letting you (our user) know when some interesting event has happened on the Stripe server. Examples include charge.succeeded, charge.refunded, invoice.paid, and so on.

With webhooks, it's easy to build something on top of Stripe events, like sending a customer receipts. This has the added benefit of allowing our users to control the entire user experience.

Stripe Connect

Stripe Connect, an API we released just last year, is another way of building on top of the Stripe platform. Connect is an OAuth2 API that allows a Stripe user to authorize access to their Stripe account to a third-party application. This application might be a marketplace, whose users want to accept payments, or an analytics dashboard, who wants to be able to have full access to Stripe data.

Provide a testing environment

One of the most important things you need with an API is a test environment. This is particularly important for a payments API— obviously your users won't want to make live charges when they're trying to test their application.

In our testing environment, we allow you to send test web hooks of any type and provide handy test card numbers that trigger certain errors (like declines). Doing this allows our users to easily test the behavior of their own application in the face of different scenarios instead of having to manually trigger things that are nondeterministic, like declines, or time-dependent, like expiring subscriptions.

If there's certain behavior that your user's application potentially depends on, make sure they can test it easily.

Help your users debug

We're developers too. We know that a large percentage of our users' time is probably spent debugging. We also (unfortunately) know that sometimes you spend a lot of time debugging something that eventually turns out to be really obvious or stupid.

For common or easy errors, you (the API) likely know exactly what's wrong. So why not try to help?

>> Stripe::Customer.create
Stripe::AuthenticationError: No API key provided.  (HINT: set your API key
using "Stripe.api_key = <API-KEY>".  You can generate API keys from the 
Stripe web interface.  See https://stripe.com/api for details, or email
support@stripe.com if you have any questions.)

Or,:

>> Stripe.api_key = TEST_KEY
=> ...
>> Stripe::Charge.retrieve("ch_17SOe5QQ2exd2S")
Stripe::InvalidRequestError: (Status 404) No such charge: ch_17SOe5QQ2exd2S;
a similar object exists in live mode, but a test mode key was used to make
this request.

If you help your users debug, they'll love you.

Dealing with Change

Lastly, dealing with change is never fun. As much as you hope you'll never have to change the API, sometimes you need to make changes, and sometimes those changes are backwards-incompatible.

There's no easy answer to versioning APIs. We keep a version per-user, which reflects the state of the API the first time they made an API request. This version isn't taken into account when we add a new feature or non-breaking change (i.e. you'll always be able to use new features, regardless of what version you're on).

Whenever we make a backwards-incompatible change2, however, we bump the current version of the API (so that any new users will see the "new" changes) and take the legacy user's version into account in the relevant API code paths.

Users can choose to upgrade their versions in the dashboard (after reviewing the details changelogs, of course), or can send a version override header in any API request to test the behavior of a specific version.

Questions?

If you have any questions, feel free to email or tweet at me. Thanks for reading!

Footnotes

  1. I'm not saying that Stripe is not going to do these particular things in the future, but it's not feasible in general to try to accommodate everyone's use case.
  2. We are usually hesitant to do this.

Credit for various parts of the presentation content go to Greg Brockman, Sidd Chandrasekaran, Evan Broder, and Ross Boucher. And of course, credit to everyone at Stripe for actually doing the things I outlined in the talk.

December 21, 2012

Private methods in Ruby

Consider the following code:

(This blew my mind the other day.)

class Hello
  def public_hello
    self.private_hello
  end

  private
  def private_hello
    puts "Hello!"
  end
end

You would expect that this would work fine: private_hello is a private method, but it's being called from within the class.

Nope.

>> hello = Hello.new
=> #<Hello:0x10d0cc200>
>> hello.public_hello
NoMethodError: private method `private_hello' called for #<Hello:0x10d0cc200>
  from (irb):3:in `public_hello'
  from (irb):13

I spent an embarassingly long time trying to figure out what was wrong ("Do I just not understand how private methods work?!"), and confused one of my coworkers as well in the process of doing so.

However, it turned out to be old news. One post puts the issue pretty succinctly:

private methods can never be called with an explicit receiver, even if the receiver is self

So, the problem with self.private_hello is that the private method is being called on an explicit receiver, even though the receiver is technically the same object—you'd need to call private_hello by itself instead.

Having learned access control modifiers in Java first, I thought this was really bizarre. I guess I need to learn Ruby a little better! (: