Subscribe via RSS

The One Thing to Keep in Mind When Building an API

As companies use more and more SaaS services, APIs have become increasingly important because they allow these services to work in concert.  For us at RelateIQ, we’ve designed our API around common standard practices (REST + JSON) to make it as easy as possible for customers and 3rd party developers to integrate with our RelateIQ. On the flip side, we also put a lot of thought into how we as engineers can make it easier to build our API. Fundamentally, just like with our external API, we believe that reducing the barrier to doing the right things leads to the best and most efficient results.

Use Tried and Tested Tools Where Possible

By using existing open source tools throughout our stack, we benefit from years of testing and optimizations.  For our API, we use Dropwizard, a library out of Yammer, which is a nice combination of Jetty for the webserver, Jackson for the JSON serialization and deserialization, Jersey for clean REST-ful endpoint development, and a Dropwizard Metrics library for monitoring.  By using these tried and true code bases, we can solve a lot of our tough questions right away and focus on adding our layer on top as needed.

Ease of Adding Endpoints and Documentation

Here we have a simplified definition of one of our endpoints.

/**
* Contacts. Contacts represent people in an address book.
* They generally have contact information (name, phone
* number, email, etc) and can be linked to relationships.
*
* @model RIQContact
*/
@Singleton
@Path("/contacts")
@Produces(MediaType.APPLICATION_JSON)
public class ContactResource {
    /**
    * Get Single Contact. A GET request which pulls a specific
    * Contact by ID.
    *
    * @param personId ID of Contact to be fetched
    * @return Desired Contact
    * @errorResponse 200 OK
    * @errorResponse 404 Contact not found
    */
    @GET
    @Path("{personId}")
    public RIQContact getSingle(@PermissionedToken(permission = APIPermissions.READ_CONTACTS) IUserContext context, @PathParam("personId") String personId) {

Here, you can see how we use Jersey to set up the REST paths, path parameters via annotations and normal Java methods.  We also use Javadocs to help us generate our documentation.  Using tools like

Swagger helps us build API documentation right next to where we write our code, making it easier to maintain.

Permissions

As we develop our API, we want to control which API tokens have access to which endpoints.  This way, users can control how much access and control individual integrations have.

If you’re familiar with Jersey, you’ll probably notice the parameter,

    @PermissionedToken(permission = APIPermissions.READ_CONTACTS)  IUserContext

isn’t a normal Jersey annotation.  In the RelateIQ codebase, IUserContext is passed around anywhere we need to control user-based access to objects in our system.  For example, by querying for a specific contact with the context, the contact will only be returned if the user has access to it.

In our API, an IUserContext is only available to the endpoint methods by being injected by this annotation.  Therefore, we effectively force new development to properly permission individual endpoints while adding minimal overhead.

Metrics and Tracking

Next, we want to be able able to track and monitor API usage.  Dropwizard comes bundled with a metrics library which we use in concert with our existing StatsD timing to monitor performance of the API.  This allows us to set up dashboards to view current performance and alerts when it deteriorates below acceptable levels.

Testing

Lastly, we want to make testing as easy as possible.  Because we use Jackson to convert Java POJOs to/from JSON, it’s easy for an engineer to accidentally change the JSON schema in a backwards incompatible way.  To combat that, we’ve made it easy to make tests that compare results against JSON strings.

RIQContact res = resource.getSingle(tokenContext, person1.getId());
assertNotNull(res);
String expected = String.format(
    "{  " +
    "`id`: `%s`," +
    "`properties`:" +
    "       {   `email`: [{ `value`: `a@b.com` }, { `value`: `a@z.com` }]," +
    "           `phone`: [{ `value`: `1111111` }]" +
    "       }" +
    "}",
    person1.getId()
).replace('`', '"');
assertApiResult(expected, res);

By doing this, we can have unit tests that makes sure our API upholds the JSON-based contract we’ve established.

At RelateIQ, our engineering team strives to efficiently build high quality products.  To do that, we believe it’s important to devote time to building and using tools that make it as easy as possible to do things right.

If this is the kind of work you’d like to be a part of, we’re always looking to add to our growing engineering team! Apply here.