REST API Design Considerations

I wrapped up development for the http://nemestats.com REST API (http://docs.nemestatsapiversion2.apiary.io/) a few months ago and I wanted to take some time to reflect upon the decisions I had to make during the process. Building a REST API from scratch can be a little daunting. This blog post is an attempt to shed some light on the key considerations that go into designing and building a REST API.

tl;dr – When designing an API consider whether you need to version it, how to version it, how to secure it, how to document it, and take care to avoid breaking changes by planning ahead.

Whether to Version Your API

Deciding whether you will always maintain backward compatibility in your API is a big decision. Supporting some kind of versioning scheme (e.g. URL, headers, query string, etc.) could significantly increase the amount of time it takes to develop your API. If you are building an API for anyone other than yourself, you will most likely need to maintain backward compatibility, and hence will need to version your API. If you are building an internal API and you can easily and simultaneously update all of the clients when you introduce a breaking change, then don’t bother versioning your API.

Since the NemeStats API is intended for third party usage, the decision to version the API was a no-brainer.

When to Version Your API

You should version your API whenever there are breaking changes to it. Here are some examples of breaking changes:

  • Removing an existing service
  • Removing a field on an object returned by an existing service
  • Changing validation rules of an existing field (e.g. a field is now required, Date must be > 1980, etc.)
  • Changing the underlying behavior of a method that is not what the original API consumer expected (e.g. a POST to /api/BoardGameBlog/ now sends an email to the entire forum when it didn’t send an email previously
  • etc, etc.

When you make breaking changes, you should provide a new, higher version of your API while allowing the old one to continue functioning — at least for a reasonable amount of time. This gives consumers the ability to upgrade at will.

How to Version Your API

Troy Hunt’s blog post on API versioning does a great job of explaining the three main approaches to versioning a REST API. This is a must-read if you are interested in the topic.

I personally prefer (and chose) to version using the URL approach because it is the most obvious and explicit to the consumer. It is also fairly easy to implement in ASP.NET Web API 2, which is my framework of choice for building REST services. Shameless plug: If you are interested in versioning a .NET REST API via the URL, check out this NuGet package and the corresponding GitHub repo which we released to assist with this challenge.

At What Level Should You Version Your API

There are at least two levels at which you could version your API. The first is at the actual service/resource level. For example, imagine you have the following service for retrieving the number of items in your shopping cart:

GET /api/v1/ShoppingCartItemsCount

Once you make a change to this service you could update just this service to have a new version by changing “v1” to “v2”:

GET /api/v2/ShoppingCartItemsCount

In this example, other services would still stay at version v1 — e.g.:

POST /api/v1/ShoppingCartItem

The problem with versioning at this level is that it requires your consumers to pay very close attention to the various resources and their versions. Furthermore, it may not be clear whether you can use version 1 of one service in conjunction with say, version 5 of another.

Versioning your entire API whenever there is a breaking change alleviates this problem. In this model, if you make a breaking change to one or more services, you release a new version of the API (e.g. /api/v2/) for all services. If you are using version 2 of the API then you know that all calls within this version are compatible.

How To Secure Your API

There are two main techniques for securing an API that I’ve used multiple times:

  • Sending an Authentication Token on Each Request – This is my preferred approach to securing APIs in most cases. Basically, you provide an authentication service like POST /UserSession that accepts a username and password and returns a unique authentication token (e.g. a GUID) if the username/password combination is valid. The client then sends this token on every request to the API as part of a header or in the query string. I prefer the use of a header like X-Auth-Token for this purpose. The API controllers then handle authentication and can return a 401 Not Authorized if the token is not legit. Note: You should always use TLS (aka SSL) when using this approach. Sniffing the credentials sent to the authentication service or even sniffing the X-Auth-Token pretty much gives the hacker full access to the API — so you really need encryption here.
  • x509 Client Certificates – You can lock down your API so that only clients with a given certificate can call the service. This is particularly useful if you are building a service that will only be called by a small set of hand-picked clients. This involves generating one (or more) certificates from a trusted root certification authority that the both the client and server trust. The API then checks that each request is signed by one of these predefined certs. Having the client install certificates — while fairly easy — may not be something you want to deal with. I would not take this approach for something like a mobile app or a public API where you could have many clients.

There are certainly other approaches like OAuth/OAuth2, Active Directory authentication, sending username and password on every request, etc. You may want to investigate these if you feel your situation warrants something different.

Plan Ahead to Avoid Breaking Your API

There some precautions you can take to reduce the chance that you’ll have to introduce breaking changes in the future. One simple design decision you can make is to ensure that your services always return an object — never just a raw string or number.

For example, imagine that you have a service that returns the number of items in the shopping cart:

GET /ShoppingCartItemsCount

You may design the service to just return a 200 OK response with the number of items in the cart like this:

2

The problem here is that if you need to add more data to the response, you don’t have an object where you can add the extra property. In this example, imagine that you also wanted to add the cost of the items in the cart. Unfortunately, since you originally returned only the number 2 instead of an object with an attribute for the number of items in the cart, you have to change the service to return something like this:


  {
    "numberOfItemsInCart" : 2,
    "totalCost" : 15.97,
    "currencyCode" : "USD"
  }

This is a breaking change. If you had started off returning an object with a “numberOfItemsInCart” attribute, then adding the additional “totalCost” and “currencyCode” fields would not be a breaking change.

How To Document Your API

The options for documenting a REST API have really matured in the last couple of years. Here are some popular options:

  • GitHub readme or GitHub wiki – Document your API in some kind of text or markdown editor from scratch (yuck!)
  • Swagger.io – Swagger is an open source framework for APIs that provides tooling to assist with visualizing your API. Swagger also has it’s own spec for building API documentation: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md
  • Apiary.io – Apiary is another framework for documenting APIs and uses a different spec called API Blueprint.
  • JSON API – JSON API is a spec that provides standards for building APIs. So far I’ve found it to be complicated but very comprehensive. If you are building an ultra-robust, gigantic, enterprise API then you might find this more appealing.

Since this space is rapidly evolving, you’ll have to do your own research to decide which one is best for you. I chose Apiary.io and have been pleased enough with the results. It’s been a little buggy, but support has been good.

Conclusion

Starting an API from scratch can be an overwhelming task. Being aware of a few important considerations can help you plan for the future. Deciding whether and how to version your API, how to secure it, how to document it, and how to avoid a few pitfalls can help you make sure your API is robust and subject to fewer breaking changes in the future.

Displaying Currency in ASP.NET

tl;dr Complete copy-and-paste C# code example at the bottom

I recently had a bad experience trying to display currency amounts in ASP.NET. Sure, displaying an amount as a currency in a given culture is simple if you assume that the amount is in the default currency type for that culture. However, this could be a really bad assumption if you are building software that serves an international market.

For example, let’s assume you have a decimal representing 1,000.00 US dollars:

decimal oneThousdandDollars = 1000.00m;

To display this amount to someone in the United States — where the US dollar is our default currency — is really quite easy:

var unitedStatesEnglishCulture = new CultureInfo("en-US");

string formattedCurrencyAmount = oneThousandDollars.ToString("C", unitedStatesEnglishCulture);

Console.WriteLine(formattedCurrencyAmount);

//output is $1,000.00

So it is in fact really easy to take a raw decimal and get a nicely formatted currency amount that looks right in the given culture. But what if we want to show 1,000 US dollars to someone in Canada, Mexico, Australia, or Columbia where they also use the same “$” local currency symbol?

An obvious example of this would be a website that sells to other marks but only transacts in one currency (e.g. US dollars). A visitor from Mexico perusing your online catalog may invoke the following code:

var mexicoSpanishCulture = new CultureInfo("es-MX");

string formattedCurrencyAmount = oneThousandDollars.ToString("C", mexicoSpanishCulture);

Console.WriteLine(formattedCurrencyAmount);

//output is $1,000.00

Do you see the problem? In Mexico the “$” means Mexican Pesos — which are worth about 1/16th the US dollar and hence the user may think the item is for sale for 1/16th of its actual price. Using the stock ASP.NET currency formatting doesn’t allow you to specify the ISO currency code of the amount. This then assumes that the amount is in the local currency of the culture you are using to display the amount.

1 USD is about 2,900 COP

1 USD is about 2,900 COP

Perhaps the simplest solution is to always use the 3-character ISO 4217 currency code instead of the local currency symbol. You probably won’t have any problems worldwide if you just display your currencies like this:

USD 1,000.00

MXN 1,000.00

GBP 1.000,00

The code for a decimal extension method for this would something like this:

public static string ToFormattedCurrencyStringWithIsoCurrencyCode(
    this decimal currencyAmount,
    string isoCurrencyCode,
    CultureInfo userCulture)
{
    string formattedNumberOnly = currencyAmount.ToString("N2", userCulture);

    //just display the 3-character ISO currency code followed by a space, followed by the numerical
    // amount formatted for the user's culture
    return string.Format("{0} {1}", isoCurrencyCode, formattedNumberOnly );
}

But let’s take the case of the average United States consumer. While most people would probably understand a “USD” prefix on an amount, we are generally much more comfortable and familiar with the dollar sign (“$”).

What if we want to beef up our extension method a bit and show the local currency symbol (e.g. $, ₽, £, etc.) when the user is viewing a currency that matches their own culture, but show the 3-digit ISO currency code when viewing a currency that is NOT local?

In other words, when an American looks at 1,000 US dollars they want to see:

$1,000.00

But when an American looks at 1,000 Columbian pesos they want to see:

COP 1,000.00

This way there is no ambiguity about whether the amount is in Columbian pesos or US dollars.

This code is a little bit more complicated, but still not too bad:

public static string ToFormattedCurrencyString(
    this decimal currencyAmount,
    string isoCurrencyCode,
    CultureInfo userCulture)
{

    //get the default ISO currency symbol for the user's culture
    //a RegionInfo requires a culture that is not neutral (i.e. it has a country associated)
    var userCurrencyCode = new RegionInfo(userCulture.Name).ISOCurrencySymbol;

    //if we are displaying a currency that matches the user's default currency,
    // then go ahead and display the currency using the local currency symbol
    if (userCurrencyCode == isoCurrencyCode)
    {
        return currencyAmount.ToString("C", userCulture);
    }

    //otherwise just display the currency with the 3-digit ISO code in front of the number
    return string.Format("{0} {1}", isoCurrencyCode, currencyAmount.ToString("N2", userCulture));

}

Voila! Now we have a pretty nice extension method for displaying currency amounts in the fashion that is best for the user. This code will take the currency code into account and won’t inadvertently change the amount into some other currency.

Technically we should add some additional protections to ensure that the isoCurrencyCode and userCulture are not null.  Since a RegionInfo requires a culture that is not neutral, we should also check this on the userCulture that is passed in. A valid culture would have a name like “en-GB” — which includes the language and country. A neutral culture would be something like “en” — which only includes the language. The code at the end of this blog entry is a little more polished with these protections in place.

This code below should display a given decimal amount in a fashion that is best for the given user’s culture.

Finished Code Example

public static string ToFormattedCurrencyString(
    this decimal currencyAmount,
    string isoCurrencyCode,
    CultureInfo userCulture)
{
    if (string.IsNullOrWhiteSpace(isoCurrencyCode))
    {
        throw new ArgumentNullException("isoCurrencyCode");
    }

    if (userCulture== null)
    {
        throw new ArgumentNullException("userCulture");
    }

    if (userCulture.IsNeutralCulture)
    {
        throw new ArgumentException(@"userCulture must not be a neutral culture. There must be a country and language so             the currency can be displayed correctly.",
        "userCulture");
    }

    var userCurrencyCode = new RegionInfo(userCulture.Name).ISOCurrencySymbol;

    if (userCurrencyCode == isoCurrencyCode)
    {
        return currencyAmount.ToString("C", userCulture);
    }
    return string.Format(
        "{0} {1}", 
        isoCurrencyCode, 
        currencyAmount.ToString("N2", userCulture));
}

Finished  Unit Tests To Go With It!

[Test]
public void It_Throws_An_ArgumentNullException_If_The_Currency_Code_Is_Null()
{
    //Arrange
    decimal SOME_DECIMAL = 1m;

    //Act
    var actualException = Assert.Throws(() => SOME_DECIMAL.ToFormattedCurrencyString(null, new CultureInfo("en-US")));

    //Assert
    Assert.That(actualException.Message, Is.EqualTo(new ArgumentNullException("isoCurrencyCode").Message));
}

[Test]
public void It_Throws_An_ArgumentNullException_If_The_Culture_Info_Is_Null()
{
    //Arrange
    decimal SOME_DECIMAL = 1m;

    //Act
    var actualException = Assert.Throws(() => SOME_DECIMAL.ToFormattedCurrencyString("USD", null));

    //Assert
    Assert.That(actualException.Message, Is.EqualTo(new ArgumentNullException("userCulture").Message));
}

[Test]
public void It_Throws_An_ArgumentException_If_The_Culture_Is_A_Neutral_One()
{
    //--Arrange
    const decimal SOME_DECIMAL = 1000.00m;
    var expectedException = new ArgumentException(
@"userCulturemust not be a neutral culture. There must be a country and language so the currency can be displayed correctly.",
"userCulture");

    //--Act
    var actualException = Assert.Throws(() => SOME_DECIMAL.ToFormattedCurrencyString("USD", new CultureInfo("en")));

    //--Assert
    Assert.That(actualException.Message, Is.EqualTo(expectedException.Message));
}

[Test]
public void It_Returns_A_Number_Formatted_For_The_Current_Users_Locale()
{
    //Arrange
    const decimal SOME_DECIMAL = 1000.00m;
    
    //Act
    string actualResult = SOME_DECIMAL.ToFormattedCurrencyString("USD", new CultureInfo("bg-BG"));

    //Assert
    Assert.That(actualResult, Is.StringContaining("1000,00"));
}

[Test]
[Ignore("You need to have the en-RU custom culture registered for this test to pass.")]
public void It_Returns_A_Number_Formatted_For_The_Current_Users_Locale_Even_If_The_Culture_Is_A_Custom_One()
{
    //Arrange
    const decimal SOME_DECIMAL = 1000.00m;

    //Act
    string actualResult = SOME_DECIMAL.ToFormattedCurrencyString("RUB", new CultureInfo("en-RU"));

    //Assert
    Assert.That(actualResult, Is.StringStarting("₽"));
}

[Test]
public void It_Returns_The_Local_Currency_Symbol_If_The_Users_Cultures_Currency_Matches_The_Passed_In_Currency_Code()
{
    //Arrange
    const decimal SOME_DECIMAL = 1000.00m;

    //Act
    string actualResult = SOME_DECIMAL.ToFormattedCurrencyString("GBP", new CultureInfo("en-GB"));

    //Assert
    Assert.That(actualResult, Is.StringStarting("£"));
}

[Test]
public void It_Returns_The_ISO_Currency_Code_If_The_Users_Cultures_Currency_Does_Not_Match_The_Passed_In_Currency_Code()
{
    //Arrange
    const decimal SOME_DECIMAL = 1000.00m;

    //Act
    string actualResult = SOME_DECIMAL.ToFormattedCurrencyString("GBP", new CultureInfo("en-US"));

    //Assert
    Assert.That(actualResult, Is.StringStarting("GBP"));
}

I hope this article helps explain the pitfalls of default currency handling in ASP.NET and provides you with some useful code to work around these more complex scenarios.