How We Designed Our Public API
Public APIs are crucial for enabling deep integration of your service into other applications and platforms. And once you become embedded into a critical mass of platforms, you’re set. The scary part is designing them to strike a balance between ease of use, flexibility, and options for future expansion.
At both iOSDevCamp 2011 and MongoSF 2011 I gave a presentation that included details about how we designed our REST API. I had looked at a ton of APIs that app developers were accustomed to using such as Foursquare, Facebook, and Twitter and took parts from each that I liked. Here are some of the thought processes we followed to design V1 of the Cocoafish API.
In the beginning we started with Ruby on Rails’ default CRUD methods for each object: create, read, update, and delete. For example by using the proper HTTP method, you can get four actions from the same URI.
Create a user: POST /users.json Show a user: GET /users/<id>.json Update a user: PUT /users/<id>.json Delete a user: DELETE /users/<id>.json
If your API doesn’t require additional actions, this is simple to use and works well. However, as soon as you add a couple of other actions:
Search users: GET /users.json Checkin a user: POST /users/<id>/checkin.json
It gets more confusing. Now you have even more actions sharing the same URI scheme. And there’s the oddball method (checkin) that has a verb in the path.
As our API grew we made everything consistent by always including a action verb in the path. The HTTP methods (POST, GET, PUT, DELETE) still determine how the data is being handled, however the verb always makes it clear what is being done.
Create a user: POST /users/create.json Show a user: GET /users/show.json Update a user: PUT /users/update.json Delete a user: DELETE /users/delete.json Search users: GET /users/search.json Checkin a user: POST /checkins/create.json
Notice that checkin has been moved to its own top-level action. Avoiding nested URIs makes things less confusing and adds flexibility. Also, we ditched the REST convention of placing the identifier of the object being acted upon in the URI. When working on the Cocoafish iOS SDK, Wei realized that it’s much simpler to create generic methods that call our server when the IDs are removed.
Therefore, object IDs are always arguments, and the full URI to checkin a user to a place becomes:
POST http:// api.cocoafish.com/v1/checkins/create.json?user_id=1&place_id=100
The flexibility afforded by taking all arguments out of the URI can be seen when we want to checkin the user to an event instead:
POST http:// api.cocoafish.com/v1/checkins/create.json?user_id=1&event_id=100
By not creating complicated URI paths for every combination of object types and actions, we greatly simplify the API.
Another important aspect to consider when designing an API is versioning. Someday you will need to make incompatible changes to your API. If you can’t synchronously update your clients when the API changes (Words with Friends really annoys me by forcing app updates), then you’ll need to version. Two common ways of doing this are to place it at the beginning of the URI or include it as a parameter:
http:// api.cocoafish.com/v1/places/search.json http:// api.cocoafish.com/places/search.json?v=1
Either way works fine, but I prefer the former since it can’t be forgotten nor mixed up with other parameters.
MongoDB is our Best Friend
The rise of web services with millions of users like Facebook and Twitter has brought about the term “Internet scale.” At Cocoafish we’ve built a server platform for mobile apps that can automatically handle Internet scale traffic for app developers so they don’t have to put a second of thought or work into it.
When designing our fundamental architecture we needed to find a database that could deal with millions of users across thousands of apps that will use Cocoafish. I knew that we would need replication, sharding, read slaves, etc. Using the default SQL solution (MySQL) was out of the question since the administrative model for these kind of scaling features is difficult. Plus I found anecdotal evidence that performance hits a wall once you get to some millions of rows in a table. On top of that we wanted to handle flexible app data without predefining schemas or running database migrations from our Ruby on Rails stack.
I first looked at Apache Cassandra which introduced me to the concept of a high-performance, schema-less document store. It looked promising, but the state of RoR support for Cassandra in late 2010 wasn’t encouraging. Then some friends at large company told me that they were doing the same type of investigation into SQL and NoSQL solutions for a major project. They had found that Cassandra just fell over with too much load. However, MongoDB looked very promising. I think in the end they chose Oracle running on expensive hardware, presumably because they could get a large, established company to be on the hook when things went wrong. But for a new startup, MongoDB looked just right.
After getting over how ridiculous the name sounded, I checked out mongodb.org, browsed the docs, and tried the software. Wei and I also attended MongoSV 2010. We were impressed with how simple and straightforward it all was. Any software that’s just contained in a single binary instead of a multitude of libraries and config files gets lots of extra points in my book. 10gen makes money from consulting, but they sort of defeat their own purpose by making the software and docs so simple to use.
However, we have purchased two sessions of their $300/hr consulting services. At first it looks really pricey, but both sessions were totally worth it. Both times we talked to Kyle Banker who wrote and maintains their Ruby driver. He really helped us with in-depth design issues and kept us on the path to MongoDB awesomeness. I also gave a lightning talk at MongoSF 2011 which was a great chance to share our experience with using MongoDB for a multi-tenant service through Ruby on Rails.
At the risk of this sounding like a paid blog entry, our experience so far has been wonderful. We’re constantly expanding our data models, and MongoDB makes it totally easy to do that. While we haven’t yet pushed all the boundaries of their performance and features, we’re confident that MongoDB will handle what we throw at it. With the recent addition of features like journaling and spherical geospatial search, this is a great time to try it out for services large and small.
Cocoafish @ iOSDevCamp 2011
Cocoafish just sponsored iOSDevCamp 2011. So Andy, Wei, and I were there this entire past weekend to help teams add network features to their apps without having to deploy any servers or write server code. Checkout this video of Dan Zeitman demoing iMemorial, an awesome app that was developed just over the weekend by a great group of people. Jump to 24:53 to hear where Dan gives props to Cocoafish.
It’s all about our Users and their Apps
Cocoafish has a single purpose: to help app developers make their apps faster and with more features then they can already. Whether it’s for an individual developer just starting out with iOS programming or an experienced team, we’re taking care of the server-side programming and administrative work so that they can focus on their apps’ features and users.
This isn’t just a toy or add-on to some other product. We’re going deep into features, performance, and security to become the one-stop, cross-platform shop for your app’s networked features. Have some great ideas for product features? Email us at email@example.com.