All Posts

From Zero To Swagger: Retrofitting an Existing REST API to an API Specification Using Swagger

Introduction

Here at uShip, our web services have gone through quite a change over the last few years. We have gone from SOAP based services to a RESTful API. Recently we dipped our toes into Swagger, an API specification framework, to provide more value to our developers and external partners.

Why Swagger?

We originally started to experiment with Swagger because we heard great things about swagger codegen, a tool that automatically generates client libraries from a Swagger specification. We had an internal SDK for consuming our own APIs that we built by hand in C#. Every time someone had to consume an internal API, someone had to add to this SDK and go through the code review process as we do with every other line of code. After a while, we started to notice that the code that was manually written conformed to a pattern that could easily be reproduced by a machine. Combine that with various other tools that integrate with Swagger specifications, and we just had to try it out!

How we did it

Because we have more than a handful of existing APIs, we thought that manually writing a Swagger specification by hand would not be worth our time. Luckily, we found Swashbuckle, a library that integrates with our .NET Web API implementation to automatically discover APIs and generate a spec. After getting the kinks out of that integration, we had a valid spec and were able to generate usable SDKs in a couple of languages. We were hooked!

What we learned

Going into this, we simply expected to make use of the tools that Swagger provides. It turns out that we actually learned quite a bit about our API and its implementation.

We weren’t consistent

  • We try to reuse as many objects in our API as possible. Because we’re in the shipping industry, one of our most common reusable inputs is an address that looks like the following:
    {
    "postalCode": "78731",
    "country": "US",
    "type": "Residence"
    }
    

    One of our resources was actually using the following for input:

    {
    "postalCode": "78731",
    "country": "US",
    "type": {
    "value": "Residence",
    "label": "Residence"
    }
    }
    

    This object does indeed exist in our API, but should have only been used for outputs. By the time we found this discrepancy, we already had too many clients consuming the resource to be able to change it.

    We have since added an integration test in our codebase that scans all APIs and verifies that only GETs and PUTs can use the output address.

  • We try very hard to make sure that our APIs and their implementations follow internal documented standards. What we failed to do was enforce all those standards via some form of automated testing. Here is an example:
    using System.Web.Http;
    
    namespace uShip.Api.Controllers
    {
    public class EstimatesController : ApiController
    {
    
    }
    }
    

    Above is our controller that receives requests from our POST /v2/estimate resource, an API that allows you to calculate a rough estimate of how much it would cost to ship your anything from anywhere to anywhere. We try to follow the convention that controllers should be named directly after their resource. We slipped up in this case, and named the controller EstimatesController instead of EstimateController.

    What does this incredibly minor inconsistency have to do with Swagger? By default, Swashbuckle will use controller names to generate a set of classes for a client library. Someone wanting to consume the POST /v2/estimate resource through the client library would have to do the following:

    var api = new EstimatesApi();
    api.EstimatesPost(/*POST body*/);
    

    This could confuse the client, especially in a dynamic language.

    Again, we have added an integration test that scans all APIs and verifies that routes match controller names. Since we don’t have to worry about breaking clients when we rename controllers, we were able to make these changes right away.

  • We never thought to follow a pattern when naming the server-side C# classes that are used for deserializing request bodies. If we had a resource called POST /v2/Nouns, we could have any of the following class names:
    • NounInput
    • NounModel
    • NounInputModel
    • PostNounModel
    • NounCreationRequest (not even kidding)
    • ReubenSandwichModel (alright, kidding a little bit on this one)

    The above is not only a nightmare for discoverability when investigating an existing API, it also makes for a terrible situation for Swashbuckle. Swashbuckle reuses the class names for the models it will use in the client libraries. While autocompletion in the IDE kind of hides this problem, it’s still not very nice for the client to deal with.

    We haven’t written an integration test for this particular issue quite yet, but writing such a test or anything like it is trivial to do with Web API.

We should have started with an API specification

We wouldn’t have had any (or at least as many) of the mistakes as we did above had we started with some form of an API specification. Having one source of truth for our API would have saved us so much headache when compared to our collection of API “definitions” scattered throughout our issue tracker tickets, acceptance tests, lacking documentation, and API developer knowledge.
When we started creating our API, API definition languages weren’t very feature complete. Now, with Swagger being the official API description language of the Open API Initiative, we all have the tools necessary to do things right from the get-go.

Caveat

Even after we created something amazing, we realized that we didn’t create an API specification. What we created was a way for our API to produce a convenient byproduct. Any “specification” we automatically generated would have just been a self-fulfilling prophecy. APIs should be built to spec; specs should not be built from APIs (at least in the long term). We have toyed with the idea of taking more of a design-first approach to building APIs, especially as we start building out APIs for our new microservices. But currently, we are content with what automatically-generated, retrofitted Swagger has given us. You should give it a try!

[amp-cta id=’8486′]