Convention-based HTTP API on top of Nancy (with documentation!)
I mentioned before that I'm rebuilding the Octopus Deploy web portal to be API-first. The goal is that all of the user functionality will be provided through the HTTP API, with the web UI using JavaScript to communicate with the API. This post is an update on how that is progressing.
Lots of resources
The Octopus API is closer to a service like Xero than it is to a service like Twitter, because we have a lot of different types of resources:
- Environment groups (new in 2.0)
- Environments
- Machines
- Machine roles
- Project groups
- Projects
- Releases
- Deployments
- Deployment processes (new in 2.0)
- Deployments
- VariableSets (new in 2.0)
- Tenant groups (new in 2.0)
- Tenants (new in 2.0)
- Retention policies
- Events (used for auditing)
- Feeds
- Users
- Teams (new in 2.0)
Most of these resources support basic CRUD operations - get a single item, list them (with pagination), create them, edit them, delete them. Then they also have some custom operations like changing the sort order of a bunch of items, or logging in, or finding resources owned by another resource.
Defining the API
I started out building the API as basic Nancy modules, but quickly found myself copying and pasting and renaming since so many of the actions were the same. I also had no idea how I would document all of these operations.
My next strategy was to look at the commonality between the different API operations to try and create a set of reusable implementations. So far I've been able to reduce the API down to a small DSL-like language for API definition:
(You can view a larger example of this here)
Each action type (e.g., Create<,>
) returns an object which describes the API operation - mostly metadata. This description also specifies a class that implements the operation, which is used when the operation is invoked.
This DSL is processed, and then a custom Nancy module acts as an "adapter", surfacing the API operations as Nancy operations.
Documentation
With the DSL in place I can now generate documentation for the API. Here is an example of what the documentation page for environments will look like. The notes and annotations on each of the operations and resource types all come from metadata that describes the different conventions I'm using to define the API.
Testing
Since the API is going to become so central to Octopus 2.0, it's also going to have a test harness. The harness is a C# console application that starts up an Octopus server, and exercises each of the API endpoints:
The API tests make use of a C# client for the API that we're also going to provide on NuGet when Octopus 2.0 ships. That means as a .NET developer you'll be able to use our library to integrate with Octopus rather than going to the raw HTTP API.
Conclusion
It still has a way to go and the API probably only covers a third of what it will eventually need to cover, but the results are looking positive so far. On the one hand, I worry that trying to generalize everything is going to make the application much more complicated. On the other hand, eliminating code duplication is usually always a good thing.
I'd love to hear your experiences when it comes to building API's in a simple and documentable way.