Ramlficated API

Intro .. first p! Inspired by Martin Fowlers blog post Refactoring to an Adaptive Model[1] I developed an interest in configuration driven development and code execution. He refactored code into JSON (model) and code (controller). It results not only in less code, but also in a system where changes in processing ruled on configuration level.

An application with most logic and knowledge in configuration file is also way easier to adopt for other languages. All you need is a handy configuration-format and a parser in that language. JSON as mentioned above is not the best option for me for the following reasons: to many extra signs, no comment blocks and no variable patterns or templates inside. What about YAML instead - nothing, great.

We use YAML settings at work as an interchange description layer between ruby and python already. Yamls plus: it already offers reusable structures, comments and less quotes and brackets. With my good feelings about it I stumpled upon something called RAML the Rest-API-Markup-Language[3]. RAML based on YAML but with extension to describe API handlers - request / responses.

So I have written a Rest API description in RAML-format. Which is processed by Ramlfications[4] and transformed into Flask and Tornado web server routes. Plus another class to interface with a database. I am using elasticsearch and pyelasticsearch[5] module.

Code is on github: https://github.com/rebeling/ramlficated-api

Describe the Rest API

We see a slice of the RAML specification file with a description of the documents endpoint.

# part of the documents-API.raml describes the documents endpoint for post
/documents:
  post:
    description: Add a document(s) to database.
    body:
      application/json:
        schema: !include document-schema.json
        example: !include document-example.json
    responses:
      201:
        description: document has been successfully created.
      409:
        description: document already exists.

/douments is root, its child is the http method with definitions for request (see body) and possible responses. Schema describes the expected input structure and example is used in the documentation and testing. Thats it.

Create the application

Let us parse the file and transform it into a format we need for the Flask application. We provide a dict of endpoints (see flask_resources) instead of the ramlfication object.

API = ramlfications.parse('ramls/documents-API.raml')
flask_resources = flaskify_ramlfication(API)

flask_resources = {
    '/documents/<id>': {
        'PUT': ResourceNode(method='put', path='/documents/{id}'),
        'DELETE': ResourceNode(method='delete', path='/documents/{id}'),
        'GET': ResourceNode(method='get', path='/documents/{id}')
    },
    '/documents': {
        'POST': ResourceNode(method='post', path='/documents'),
        'GET': ResourceNode(method='get', path='/documents')
    }
}

Then we use Flasks add_url_rule to apply the resources to our app. Raml_view based on flask class View extends our RamlowView and overwrites the dispatch_request. It retrieves the calls, process and return the defined status.

for rule, resources in flask_resources.items():
    app.add_url_rule(rule,
                     view_func=raml_view.as_view(rule,
                                                 resources=resources,
                                                 API=API),
                     methods=resources.keys())

RamlowView evaluates the request and communicates with our database. The databaseAPI is a class of its own again to switch db easy. The db operations in the following at the DatabaseAPI class:

  • Create - POST
  • Read - GET
  • Update - PUT
  • Delete - DELETE

Post a document

The example POST when new document retrieved. It is validated and then added via db_API. Finally we create response object from the db_API return info.

if request_method == 'POST':
    doc = json.loads(request.data)
    validate(doc, resource.body[0].schema)
    success = self.db_API.add(doc)
    return self._response(success, doc, responses)

The database API is implemented as needed from db and library.

def add(self, doc, index=None, doc_type=None):
    """POST case: add new document."""
    index, doc_type = self._resolve(index, doc_type)
    res = self.es.index(index, doc_type, doc, id=doc.get("id"))
    success = True if res['created'] else False
    return success

Generate documentation

The documentation is generated[6] from the raml-file which is pretty cool we have not only the describtion there, but also defined the models, params and examples. Changing the API stati, model, etc. via raml-file updates your documentation en passent.

Check out the generated documentation https://rawgit.com/rebeling/ramlficated-API/master/docs/documents-API.html

References

[1] Refactoring to an Adaptive Model http://martinfowler.com/articles/refactoring-adaptive-model.html

[2] YAML http://www.yaml.org/

[3] RAML http://raml.org/

[4] Ramlfications https://ramlfications.readthedocs.org/en/latest/

[5] PyElasticsearch https://pyelasticsearch.readthedocs.org/en/latest/

[6] raml2html https://www.npmjs.com/package/raml2html