Want to Build a Better Web API? Build a Client Library!
A solid web API can be an important thing to have. Not only is it great to give users direct access to their data, but exposing data and operations via a web API enables your users to help themselves when it comes to building functionality that doesn’t really make sense in the application itself (or functionality that you never really thought of). It’s also a great way for users to get more familiar with your service.
However, if your API sucks, you can rest assured that nobody will touch it. We’ve all had to deal with crappy web APIs, the ones that make you jump through hoops in order to perform a task that should be dead simple to do. Web APIs should make the simple tasks easy, and the hard tasks possible. To add to the challenge, APIs are notoriously difficult to change. Even with a solid versioning scheme, it is often a real chore to get your users to stop using the deprecated API in favor of the new version. So, it’s important to do a good job the first time.
When building a web API, identifying the tasks that one might want to perform can sometimes be difficult to see when you’re surrounded by JSON, XML, GETs, POSTs, PUTs, DELETEs, and HTTP status codes. While it can be easy to see what single actions you would want to expose, seeing how those actions may interact with each other can be much more difficult. Sometimes you need to take a step back, away from the land of HTTP, in order to see your API as another programmer would see it.
Building a client library that wraps your web API is a great way to do this. It’s relatively easy to imagine how your requests and responses could be represented as objects. The largest benefit of this exercise is to take it a step further, and give the user of your client library the ability to determine what they should do next. Simply knowing if an API call succeeded or failed is usually not enough. Users of your client library need to be able to determine why the request failed, and understand what they can do about it. This extends well beyond the lifecycle of a single HTTP request and response.
Communicating errors
There are several different ways to communicate errors to the user. The proper use of HTTP status codes is one such way. The 4xx class of status codes are specifically intended to be used to communicate that something was wrong with the client’s request. If your API methods are simple, and specific in their purpose, you may be able to rely on HTTP status codes alone to communicate the various causes of failure to the client.
If your API method is complex, and could result in many different failure scenarios, you should first try to break it down into smaller, more specific API methods :) If that can’t be done, then another option is to return some easily parseable text in the response body (JSON or XML) that includes an error code that identifies the specific failure scenario. The response body could be as simple as:
{ "error_code" : 123 }
You could also provide a description of the error in the response as well. This helps users getting started with the API, saving them from having to constantly refer to your API’s documentation every time they get an error:
{
"error_code" : 123,
"error_message" : "A widget with that name already exists"
}
The important thing is that all failure cases be easily identifiable via a specific, documented code (HTTP status code or custom error code). Error messages should be seen as purely supplemental information. At no point should your users have to parse the error message to determine what happened, or what they should do next.
Isn’t this the same as “dogfooding”
Not exactly. Dogfooding simply involves using what you have created. You could easily dogfood your web API by firing HTTP requests at it using a simple HTTP client library. It is not until you need to take different actions based on different responses that you really start to see if you are properly communicating the result of the request. Building a client helps with this because it forces you to think about the different results and error scenarios in order to decide how your client should handle them. Which failures should raise exceptions? What sort of exception should be raised? How should non exceptional failures be communicated to the caller?
The next step in this process would be to build an application that uses your client library. That step could help identify issues with your client library, just like building the client library helps identify issues with your web API.
The client library
Oh, and don’t forget. At the end of the day, you’ll end up with a better designed web API, AND a great client library that your users can use to interact with your system. Not a bad deal!
Professionals Act Professional
I’m sick of it.
Every week (at least it feels that way) some new drama rears its ugly head in the ruby community. Petty arguments via twitter, one ranting blog post after another, people mocking ideas they consider less than ideal, and even some personal attacks thrown in the mix. There’s so much drama in fact that there is now a site out there that lists it all for the rest of the world to see.
Seriously? Are we all still in junior high?
Just think for a minute about all of the time and energy we are wasting here. Instead of igniting these flame wars, from which nothing productive is ever achieved, we could be growing as a community. We could be bringing up the next generation of software developers. We could be positively encouraging others to build better software. We could be sharing our experiences with others. We could be leading by example.
For a community of people complaining that they’re not treated like professionals, we sure don’t act very professional. If this is the way we behave, can we honestly expect people to treat us with the respect that they treat doctors, accountants, teachers, and members of other professions?
If you want to be treated like a professional, it’s best to start acting like one first.
Take the high road for once. The view is much nicer.
ReloadablePath in Rails 3
A core feature of the Signal application is support for custom promotion web forms. Custom promotion web forms allow our customers to create custom web pages that will allow their customers to interact with their promotions via the web. Our customers currently use these web forms for sweepstakes entry, email/SMS subscription list opt-ins, online polls, and more.
One of the best things about custom promotion web forms is that it allows our customers to completely control what the web page looks like. The Signal application allows for the creation of a web form theme that can be used as a template for a given promotion. The specific promotion can then customize the web page further by specifying the copy that appears on the web page, the data attributes that should be collected, and more.
The web form themes are managed by the Signal application, and are saved to disk as a view (an ERB template) when created or updated. Our customers can edit these themes at any time. When a theme is updated, we need to tell Rails to clear the cache for these specific views, so our customer will see their changes the next time they visit a web page that uses the updated theme.
In Rails 2, this was done using ReloadablePath.
class SomeController < ApplicationController
prepend_view_path
ActionView::ReloadableTemplate::ReloadablePath.new(
"/path/to/my/reloadable/views")
end
However, ReloadablePath is no more in Rails 3. So, we needed to find a new solution to this problem.
Rails 3 introduced the concept of a Resolver, which is responsible for finding, loading, and caching views. Rails 3 also comes with a FileSystemResolver that the framework uses to find and load view templates that are stored on the file system.
FileSystemResolver is very close to what we want. However, we need the ability to clear the view cache whenever one of the web form themes has been updated. Thankfully, this was fairly easy to do by creating a new Resolver that extends FileSystemResolver, which is capable of clearing the view cache if it determines that it needs to be cleared.
Looking at the code for the Resolver class, you can see that it checks the view cache in the find_all method. If it does not have the particular view cached, it will proceed to load it using the proper Resolver. So, we simply have to override find_all to clear the cache if necessary before delegating the work to the super class to find, load, and cache the view.
class ReloadablePathResolver < ActionView::FileSystemResolver
def initialize
super("/path/to/my/reloadable/views")
end
def find_all(*args)
clear_cache_if_necessary
super
end
def self.cache_key
ActiveSupport::Cache.expand_cache_key("updated_at",
"reloadable_templates")
end
private
def clear_cache_if_necessary
last_updated = Rails.cache.fetch(ReloadablePathResolver.cache_key) { Time.now }
if @cache_last_updated.nil? ||
@cache_last_updated < last_updated
Rails.logger.info "Reloading reloadable templates"
clear_cache
@cache_last_updated = last_updated
end
end
end
Since we're running multiple processes in production, we need a way to signal all processes that their view caches should be cleared. So, we're using memcache to store the time that the web form themes were last updated. Each process then checks that timestamp against the time that particular process last updated its cache. If the timestamp in memcache is more recent, then the ReloadablePathResolver will clear the cache using the clear_cache method it inherited from Resolver.
Next, we need to add some code that will update memcache any time a web form theme has been updated and saved to disk.
class WebFormTheme < ActiveRecord::Base
after_save :update_cache_timestamp
private
def update_cache_timestamp
Rails.cache.write(ReloadablePathResolver.cache_key, Time.now)
end
end
The final step is to simply prepend the view path with the new ReloadablePathResolver.
class SomeController < ApplicationController prepend_view_path ReloadablePathResolver.new end
References
Beware the Hack!
Hacks are dangerous little creatures. They live in the darkest, dustiest corners of your application, forgotten about, waiting… Waiting for the chance to rear their ugly little heads, open their disease infested mouths, and sink their jagged teeth into customer confidence and developer productivity.
We’ve all been there. We have a product that works great. It solves a certain problem incredibly well. Then, a well meaning customer comes along and says, “This is fantastic. It almost solves my problem perfectly. Is there any way you can modify it slightly to do X instead of Y.”
Sometimes this is no problem at all. Sometimes the design of the product is flexible in ways that make it a breeze to add this functionality. But, sometimes the request comes out of left field, and takes your product in a direction you never anticipated. While we strive to build software that is extensible and adaptable (it is software after all, isn’t it?), none of us can see the future, nor anticipate every possible customer request.
About this time you start to hear a little voice inside your head. “Well, I suppose I can hard code this, or add an if statement here, or write a one-off script to do X”. After all, you don’t want to tell your customer “No, sorry, we can’t do that”. And, they certainly don’t want to hear “Sure, we can make that modification, but it will require a significant amount of refactoring in order to ‘do it right’”.
Acting on these thoughts births a tiny baby Hack. The Hack is little when it’s born, but it certainly doesn’t stay that way. Once the Hack is born, it is much easier to add to the Hack, or feed it. With everybody modification to the Hack, it gets bigger, and bigger. Pretty soon you have a large, ugly Hack with a nasty attitude on your hands. And, despite you being it’s “mommy”, it doesn’t like you, at all. Not one bit.
Hacks are dangerous for several reasons.
First, they almost always live outside the main execution path of the code. This means they’re not executed nearly as often as the other code. Even if you have a series of tests for the Hack, nothing exercises code like constant execution by your customers. Also, because they’re not really “part of the application”, Hacks are often forgotten about when updating or fixing code.
Second, they’re usually created to quickly get around some issue. And by “quickly”, I mean “didn’t totally think this though, but I’m fairly certain that if I tweak X, alter Y, and drive it with a custom script, it should work just fine”. And, usually it does work just fine…at least in the beginning. But, this is when the Hack is still young, and under your control. Adult Hacks are not nearly as cooperative.
Third, they’re usually only known about (at least in detail) by the members of the team that created them. A Hack is like a big, puss filled pimple on your ass. You don’t go around showing those to your friends and co-workers, do you? Hacks, by definition, are quick and dirty solutions to problems. They’re not elegant, or sexy. So, developers tend to keep their Hacks to themselves. At most, a developer will mention that they hacked around a problem, but rarely do they go into details. The other team members are largely left in the dark. Not knowing where a Hack lives or how it behaves is a sure fire way to get bitten by it down the road.
Always remember, that little baby Hack…it will grow up. It will get nasty. It will bite. It’s just a matter of when and where. Those who have been programming for long enough know this to be fact. And, being nasty little creatures, Hacks usually wait until the worst possible time to bite.
So, beware the Hack! They are big, ugly, mean, have teeth, and will most certainly bite.
An Adjustment in Priorities
I think it is very common this time of year for people to reflect, especially on their accomplishments during the past year, and what they would like to accomplish next year. Personally, I’ve been doing quite a bit of thinking about my development as a software engineer.
It feels as though, over the past couple of years, the pace at which I have been learning has slowed. This troubles me. One of the reasons I love the software industry is that there is always something new to learn. I love learning about new ways to solve problems with technology. And, learning is what you must continue to do if you are to stay relevant as a software engineer.
Now, this doesn’t mean that I haven’t learned anything over the past couple of years. Quite the contrary actually. I’ve really been enjoying the boom our industry is seeing in non relational data storage systems. We’ve been utilizing many of these newer technologies at Signal with great success. I’ve been an active member of the ChicagoDB users group, attended a few conferences on this topic, and even gave a few talks…a first for me. I’ve also been learning first hand how to scale a system as we’ve taken the Signal application from one capable of sending thousands of messages per “blast” to one that regularly sends millions of messages per “blast”.
However, something is missing. I used to read more. I used to blog more. I used make a sincere effort to learn a new programming language every year. I used to constantly download and tinker with new tools. These things have fallen to the wayside. And, I really miss them.
Instead, over the past couple of years, I have been spending the majority of my time creating. When creating a new project, there is usually something new to learn. That’s what drives me to create the project in the first place. But as time goes on, the cost of the time I spend working on the project starts to outweigh the benefit. I reach a point where I am no longer learning anything while working on the project. If I’m not benefiting from working on the project in any way (now that the learning part has mostly dried up), there is little motivation for me to keep working on it.
Part of me feels obligated to keep these projects going, and to get them to a point that I would consider “done”. I’ve never been a quitter, and stopping work on these projects sounds like quitting to me. However, it makes little sense to continue working on something that gives me nothing in return. I will still fix bugs that are reported, but any new development on most of my active projects is likely going to cease. Time is precious, and I need to make the most of it.
So as of right now, I’m making an adjustment in priorities. I’m using my free time to invest in myself. To read more. To blog more. To get back to learning a new programming language a year (Clojure is on tap for 2012…I’ve been wanting to learn it for 3 years now). To resume downloading and tinkering with tools that spark my curiosity. I want to start practicing how to write code, like I learned at the code retreat I attended over the summer.
I’ll still be creating, as I’m someone who learns by doing, but I have a feeling from now on my projects will be much, much smaller.
It is impossible to “find” time to do something. You have to make time.
GitHub
Most Popular Posts
Tags
Archives
- May 2012 (1)
- April 2012 (1)
- March 2012 (1)
- February 2012 (1)
- December 2011 (1)
- September 2011 (1)
- July 2011 (1)
- May 2011 (1)
- April 2011 (1)
- March 2011 (1)
- January 2011 (2)
- November 2010 (2)
- September 2010 (1)
- August 2010 (1)
- July 2010 (2)
- June 2010 (2)
- April 2010 (1)
- March 2010 (1)
- February 2010 (2)
- January 2010 (1)
- December 2009 (1)
- November 2009 (1)
- September 2009 (2)
- August 2009 (3)
- July 2009 (2)
- June 2009 (3)
- April 2009 (1)
- February 2009 (1)
- January 2009 (2)
- December 2008 (8)
- November 2008 (2)
- October 2008 (3)
- September 2008 (6)
- July 2008 (3)
- June 2008 (1)
- May 2008 (8)
- April 2008 (6)
- March 2008 (2)
Blogroll
Industury News
Other Links
My GitHub Feed
- jwood pushed to master at signal/signal-ruby
- jwood pushed to master at signal/proby
- jwood pushed to master at signal/proby
- jwood pushed to master at signal/signal-ruby
- jwood pushed to master at signal/proby
- jwood pushed to master at signal/signal-ruby
- jwood pushed to master at signal/signal-ruby
- jwood pushed to master at signal/proby-ruby
- jwood commented on pull request 9 on stripe/stripe-ruby
- jwood pushed to master at signal/proby




