This project is just a template used to document some of the best practices learned while working on a very complex Sinatra API project with Moz. This project has far fewer moving parts, but a large part of what makes Moz's model significant is how they designed their Sinatra application for maintainability and modularity.
This project is not intended to be used as a gem, but to serve as a living example of robust API architecture and design.
First, clone the project and open its directory:
git clone git://github.com/bendyworks/caravan and
./script/bootstrap will run
bundle and then create the database for you.
Using the rake tasks
All of the database tasks, except for
db:new_migrations, rely on the
rake cane # Run cane to check code quality / Check code quality metrics with cane rake db:bootstrap # A task for doing everything to set up this project rake db:console # Provide access to the database via the Sequel gem rake db:create # Create a new database rake db:drop # Drop the database rake db:migrate # Run the migrations on the database rake db:new_migration[migration_name] # Create the skeleton for a new migration rake db:recreate # Recreate the database rake spec # Run RSpec code examples
Some of the rake tasks provide convenient database combos:
rake db:bootstrapto create and migrate the database (based on settings from
rake db:recreateto drop, create, and migrate the database.
rake db:new_migration[migration_name]will generate a skeleton for a new migration on the database. For example,
rake db:new_migration[add_users]will create a migration in
0001_add_users. Unless you already have another migration.
First, bootstrap as indicated above in "Getting Started." Next, fire up the console:
Inside the console, create a user like the following:
 pry(main)> Users.create company: "Aperture Science", html_url: "http://huge.succe.ss", location: "lab", login: "chell", name: "Chell", url: "http://cake.com"
With the users created, you can now use an HTTP client like curl or Postman. Here's what a curl request would look like:
$ curl http://0.0.0.0:9292/users/chell
To get a previous version of the API (default in this sample app is "2.0"), you can do the following:
$ curl -H 'Accept: application/json; v=1.0' http://0.0.0.0:9292/users/chell
The request/response cycle
Requests to Caravan can return default (preferred) version or requested version. To request a specific version, specify the version number in the accept header. For example:
Accept: application/json; v=2.0
Some endpoints require sending parameters via the URL. Others will require them to be sent as part of the request body. All parameters sent will be validated. If they are invalid, you will receive a 403 response.
All responses return JSON. The JSON response is validated against the endpoint definitions. Response types generally are 200, 204, or 404 if the request was valid and the response body was valid. If Caravan generates a response body that does not validate, you will receive a 500 response.
More information about Sinatra and how it handles routing can be found on its website.
Interpol endpoint validation
Interpol is an open source toolkit for policing your HTTP JSON interface maintained by Moz. It uses YAML files with definitions of the expected request and the promised response. These are called endpoint definitions. Caravan uses Interpol to enforce a contract with the consumer of the JSON interface.
When a request is made to Caravan, the request is parsed and validated by Interpol. If the request is not in the form that Interpol expects, the client will receive an error. If the request is valid, Interpol will then validate the response that Caravan generates to ensure it is what the consumer expects.
Versioned Endpoint Model
Interpol allows for endpoint definitions to be versioned as such we can define independent Endpoint Models to provide different versions of an endpoint. Typically the preferred version is the highest version number so if the consumer does not specify the version they want, they will get the newest version of the endpoint.
These can be any API or HTTP service with whitch you wish Caravan to communicate.
Here's a quick look at the directory structure of the project:
. ├── bin ├── config ├── db │ └── migrate ├── lib │ ├── apps │ ├── endpoint_definitions │ ├── endpoint_models │ ├── models │ ├── tasks │ └── util ├── script │ ├── bootstrap │ └── test └── spec ├── acceptance ├── integration │ └── endpoint_models ├── spec_helpers └── unit ├── endpoint_models └── models
The importance of the directory structure is that it lends itself to keeping one's concerns separated. The importance of each directory is explained in short below, and in greater detail in the READMEs placed in each directory.
In our client's case, this part of their infrastructure provides access to a
large number of resources. As such, keeping the resources (or endpoints)
organized helps and splitting them up into several smaller apps helps with
that organization. The pattern used to keep items organized is by using the
structure of the routes. For example, if you have a route that to the end user
/users/:user_id/some_resource, you might make an app
specifically to hold all of the resources for
This directory contains all of the schema definitions for endpoint request and responses.
This directory contains all of the models that generate the data to be returned to the client. Each of these classes generates the data for one version of an endpoint. There is a distinct pattern in having multiple endpoint versions: the newest version has an unversioned name, older versions have version info in the name and inherit from the newest version. This is to illustrate the common relationship between new and old versions. It also facilitates removal of old versions.
Models here correlate directly to database models.