wiki:APIConventions
Last modified 12 months ago Last modified on 05/06/13 12:00:25

API Conventions

Following are the conventions for routes, it's parameters and output in Katello.

Routing

no duplicate routes

Besides the cases when it is needed for compatibility with subscription-manager there should always be only one route that gets you to a method in a controller.

no routes deeper than 2 levels

Routes should not be longer than:

/api/<resource>/<id>/<nested_resource>/

The rule of thumb is to use only one globally unique identifier in a route where possible.

api route definition

There is rails function resources for defining routes. By default it contains also actions :edit and :new which are useless in the api. Therefore we use our custom method api_resources that wraps it and removes the unused actions.

api_resources :organizations do
    api_resources :products, :only => [:index]
end

Input

nested input json objects

Input json parameters should be nested in a parent object.

Eg. nested:

{
  organization: {
    name: "ACME_Corporation",
    id: 1
  }
}

vs. not nested:

{
  name: "ACME_Corporation",
  id: 1
}

We chose nesting approach for consistency with controllers where we need to pass 2 objects at one call. Nested params are also usually easier to work with.

http methods usage

From the REST definition:

verb usage
get data retrieval, makes no modifications
post create new resource
put updating resources, needs to be idempotent
delete resource removal

Output

output functions

We use our custom respond functions for abstracting the output. It allows for easy changes in output format accross all api controllers.

# default respond function, calls respective specific responder according to name of the action
respond(options)

# respond to standard CRUD actions
# default status is 200
respond_for_show(options)
respond_for_index(options)
respond_for_create(options)
respond_for_update(options)
respond_for_destroy(options)

# respond with status message
# default status is 200
respond_for_status(options)

# respond with info about async task
# default status is 202
respond_for_async(options)

Respond functions take following options:

option key description
:template name of the template that will be used, teplates are searched under /views/api/:version/:resource_name/
:action same as :template
:status forces the status code
:message output message, used for v1 compatibility reasons, v2 ignores it
:resource data passed to rabl templates, by default it takes content of instance variable named by the resource (eg. @organization in OrganizationsController)
:collection data collection passed to rabl templates, by default it takes content of instance variable named by the resource (eg. @organizations in OrganizationsController)

Example:

#in class OrganizationsController
  def create
    @organization = Organization.create!(params[:organization])
    respond # responder automatically uses variable @organization
  end
  #OR
  def create
    respond :resource => Organization.create!(params[:organization]) # resource is passed directly
  end

#in class SystemsController
  def checkin
    @system.checkin(params[:date])
    respond_for_show # responder automatically uses variable @system and template show.json.rabl
  end

#in class ProductsController
  def remove_sync_plan
    @product.sync_plan = nil
    @product.save!
    respond_for_status :message => _("Synchronization plan removed.") # the message is ignored in v2
  end

error handling

All errors are cught and properly handled at one place in ApiController. If there happens exceptional condition in the controlller, appropriate exception should be risen..

common rabl templates (v2)

Version 1 uses resource's as_json methods to produce output whereas version 2 use more flexible Rabl templating system.

Controller's rabl templates are placed in a directory /app/views/api/:version/:resource_name/. There are also some common templates and partials that can be shared /app/views/api/:version/common/. They shoud be reused as much as possible.

partial description
_async_task.json.rabl common attributes for asynchronous tasks
_identifier.json.rabl id and name, optionally label and description if the resource has such attributes
_org_reference.json.rabl organization_name
_syncable.json.rabl common attributes for syncable resources
_timestamps.json.rabl created_at and updated_at timestamps


template description
async.json.rabl result of async operations, uses _async_task
create.json.rabl result of create, includes resource's show template
destroy.json.rabl result of destroy, returns empty body
index.json.rabl listing of collections, includes resource's show template
status.json.rabl result of action with no content
update.json.rabl result of update, includes resource's show template

Common templates are used automatically for their actions if no custom template is found. As it makes no sense to generalize the show template, at least it has to be always defined.

API v1 vs v2

architecture

Version 2 standardizes input and output of our api. To minimize duplicities v2 controllers inherit from v1 and override only methods that need to be changed.

selecting api version

We distinguish api versions based on information in Accept header's affix ",version=X". Default is still version 1.

curl -H "Accept: application/json,version=2"  -k -u admin:admin https://localhost/katello/api/organizations #calls v2

curl -H "Accept: application/json,version=1"  -k -u admin:admin https://localhost/katello/api/organizations #calls v1
curl -H "Accept: application/json"            -k -u admin:admin https://localhost/katello/api/organizations #calls v1

I'm creating a new controller, where shoud it go?

Until we switch to v2 as default the recommended approach is to create v1 controller in line with all the conventions above. V2 then minimizes only to class definition and a set of rabl templates.

Environments controller can be taken as an example of such case:

class Api::V2::EnvironmentsController < Api::V1::EnvironmentsController

  include Api::V2::Rendering

end

Attachments