Friday, November 29, 2013

Have a REST: building a mockup for a large RESTful service, part I.

So, You are familiar with the concept of REST and RESTful web services. You even created some already. But just imagine:

  • you are starting a new large project; there are over 20 entitites!
  • you have just a week, working with your JavaScript/UI developer, to deliver a mockup to your customer. 
Just one week and you are just two. And the UI guy depends on your backend. You must be fast. You just do not have time to implement alll these 20(30,64) REST handlers!

Have a rest. Im 90% cases, all your entities behave the same way. You already can transform your entities to JSON and back again (I already wrote about it). So why worry?

Okay, You are smart and You use Flask and Flask-RESTful


A universal real-only REST handler

First, we will create a simple readl-only REST controller template, so your UI guy can start.
from flask import abort, request
from flask.ext.restful import Resource, reqparse
from myapp.lib import database
from myapp import model as M

class ReadOnlyRestfulResource(Resource):
    Entity = None

    def get(self, id=None):
        if not id:
            query = database.session.query(self.Entity)
            return [ entity.simple_object() for entity in query ]

        entity = database.session.query(self.Entity).get(id)
        if not entity:
            abort(404)

        return entity.simple_object()
Okay, so we can create a module "views" like this - we just name each restful resource class as it's corresponding model class:
from myapp.lib.restful import ReadOnlyRestfulResource
from myapp import model as M

__all__ = ['Person', 'LegalEntity', 'Contract']

class Person(ReadOnlyRestfulResource):
    Entity = M.Person


class LegalEntity(ReadOnlyRestfulResource):
    Entity = M.LegalEntity


class Contract(ReadOnlyRestfulResource):
    Entity = M.Contract
Now, just import them in your app.py and attach routes:
from flask.ext.restful import Resource, Api

# create RESTfull API
api = Api(app)

# other app details skipped

import views

for view_name in views.__all__:
    try:
        view = getattr(views, view_name)
        view_name = view_name.lower()
        api.add_resource(view, '/' + view_name + '/', '/' + view_name, '/' + view_name + '/')
        app.logger.debug(u"Registered view {}".format(view_name))
    except Exception as e:
        app.logger.warn(e)
Great! Now, accessing /person will provide your UI guy with a list of all persons, and /contract/13 with contract #13, etc. Fill your database with sample data and you almost have your mockup alpha ready. Next you fill your database with 1000+ rows of sample Contract entities... and notice, that a list of all contracts is too large. You only want a contract id, datem and come status in the full list. So let's implement different filtering in 'get-all' and 'get-one'. Remember, our 'simple_object' method accepts a list of fields, so let's use it.
from flask import abort, request
from flask.ext.restful import Resource, reqparse
from myapp.lib import database
from myapp import model as M

class ReadOnlyRestfulResource(Resource):
    Entity = None
    fields_all = None
    fields_one = None

    def get(self, id=None):
        if not id:
            query = database.session.query(self.Entity)
            return [ entity.simple_object(self.fields_all) for entity in query ]

        entity = database.session.query(self.Entity).get(id)
        if not entity:
            abort(404)

        return entity.simple_object(self.fields_one)
And next we add some field filtering, if we want to. We can skip any or both filters:
class Person(ReadOnlyRestfulResource):
    Entity = M.Person
    fields_all = ['id', 'name', 'email']
    fields_one = ['id', 'name', 'email', 'telephone', 'legal_entity_id']


class LegalEntity(ReadOnlyRestfulResource):
    Entity = M.LegalEntity
    fields_all = ['id', 'display_name', 'vat_id']


class Contract(ReadOnlyRestfulResource):
    Entity = M.Contract
    fields_all = ['id', 'legal_entity_id', 'date_signed']
Okay, our output is much better. Next, we want some other filtering on the server-side. The reason is: we have 100+ customers, each has 100+ contracts; requesting all contracts... wow! I just want contracts for this customer!
class ReadOnlyRestfulResource(Resource):
    Entity = None
    fields_all = None
    fields_one = None

    def get(self, id=None):
        if not id:
            query = database.session.query(self.Entity)

            for k,v in request.args.iteritems():
                try:
                    column = self.Entity.__table__.c[k]
                    filter = column==v
                    query = query.filter(filter)
                except:
                    pass

            return [ entity.simple_object(self.fields_all) for entity in query ]

        entity = database.session.query(self.Entity).get(id)
        if not entity:
            abort(404)

        return entity.simple_object(self.fields_one)
Now we can request /contract?customer_id=13 - and we fetch only desired contracts.
At this point, we can grab some Faxe, Spendrups or Lapin Kulta and have a rest, while our UI guy creates his templates, etc. Or we can even help him! :)
What's next?
  • implement handling POST, e.g. creating new entities;
  • add some advanced filtering;
  • implement PATCH for updating existing entities.
So, to be continued!

No comments:

Post a Comment