At Wealthfront, we have an API server which exposes a REST API that’s consumed by our iOS and Android apps. Recently we decided to migrate our web frontend to use the REST API because we believe it would be beneficial to have all of our frontends sharing a single data source. We identified a few issues that we wanted to be sure to address as part of our frontend migration:
- the interface between client and server should be well defined
- each client shouldn’t have to implement boilerplate code to interact with the API, and
- it should be easy to maintain consistency between the client and server code
In the past, we generated the client libraries from the server code. However, we have since decided that it is not worth the effort to implement a similar framework for each client platform we have. After looking into a few existing solutions, we decided to try Swagger Codegen.
Swagger Codegen parses an OpenAPI/Swagger Definition defined in JSON/YAML and generates code using Mustache templates. To prevent engineers from modifying the generated code accidentally, we decided not to check in the generated code. Instead, we package the generated code as artifacts.
Here is the project structure we have:
- ruby-api (module)
- ios-api (module)
- android-api (module)
- jaxrs-api (module)
Under the api-schema repository,we have the schema.json file and four modules: ruby-api, ios-api, android-api and jaxrs-api. The modules all generate code from schema.json using the swagger-codegen-maven-plugin. The generated code is then packaged and published to our maven central repository at api-schema build time.
Enforcing API Standards with Mustache Template
Swagger specification allows users to document various aspects of a REST API. At Wealthfront, we prefer enforcing rules using code instead of documentation. And while working with Swagger Codegen, we found out that customizing Mustache templates is a great way of transforming documentation into code. The examples below are all based on this schema.
Enforce Required Field
In Swagger schema, input parameters for endpoints can be marked as required as in the example below:
The generated server stub would include an annotation indicating that the account_id is required:
The annotation is useful, but it is not binding. The good news is that we can easily generate a null check in code with one extra line in the Mustache template:
And the new generated code would be:
The null checks for the required parameters would allow the server code to fail early when inputs from client are invalid.
In our Android codebase, we have a convention of making model classes immutable (e.g. no setters). The reason is that the models should be initialized by the JSON deserialization library, and the UI logic should only read the values returned from our server. Then, in our tests, we fake the data using builders:
After migrating to Swagger schema, the models are generated. We also added a few lines to the template in order to generate the builders at the same time.
JsonEnumDefaultValue is a Jackson annotation that allows users to define a default value used when trying to deserialize unknown Enum values. It make sures that older versions of our Android app continue to work, even when new enum values are returned from the server. We can make sure that all enums used by the API have a JsonEnumDefaultValue annotation by adding it to the Mustache template:
Use of Vendor Extension
Lastly, we want to demonstrate the use of vendor extension, which is a way for users to specify metadata on almost anything (e.g. endpoint, model, endpoint parameter). The useful part is that we can read the vendor extensions during code generation, and translate the metadata into code.
Swagger specification does not allow the use of enum reference in input parameters. For example, if we add an enum parameter to the getAccountInfo endpoint:
Even though we already have an enum AccountType defined in definitions, we cannot link the account_type query parameter to the AccountType definition. And account_type would be a String in the generated code:
In this case, we can add our own vendor extension to the “account_type” parameter to indicate it is an AccountType enum.
Then during the code generation, we can override the original type (e.g. String) with the type defined with “x-enum-definition”.
Enforce API Standards with Tests
The other thing which we really like about Swagger Schema is how easy it is to enforce API standards through tests. Swagger Parser is a tool for parsing the schema into POJO. Using this tool, we are able to access all the operations/models defined in the schema programmatically, and make sure that they are in good shape. Here is one example of such a test:
We run these tests during the build of api-schema, and found them to be much better than pull request comments in enforcing API standards.
We started using Swagger Codegen in the hope of removing code duplication between our server and clients. Ultimately, Swagger Codegen provided our team even more additional capabilities than that. It has allowed us to establish a better standard for our API. Perhaps most importantly, Swagger Codegen enables us to encode those standards into code, as opposed to relying on code review and manual documentation.