Service Industry

02 September 2013

At Tangent Labs we've come startlingly close to standardising the service layer.

Have you ever wondered how the clipboard works? I mean the ctrl+c-ctrl+v service that we, all of us, use every day, for a service it is: a module belonging to none of your apps in particular and belonging to all of them. It may seem like a simple, even a trivial thing, but give it a moment's thought – how many times have you copied and pasted, say, an image from a website into a rich text document without the appropriate tingle of appreciation that this system just works? How many times in the last month alone have you selected a block of columns and rows in a spreadsheet and dragged it into an e-mail, or vice versa, without even giving it the dignity of your recollection a month later?

The clipboard is the perfect service layer – it's ubiquitous, it's robust, it's extensible, it's transparent. Any piece of software on the same machine can go to the clipboard and expect certain things to be waiting there. It fulfills a contract so perfectly that it disappears: imagine someone brings you a new operating system to play with, and you ctrl+c-ctrl+v, and it does nothing. Imagine your outrage. This kind of definition of “service” might have telling analogies to the material market and life in our day and age – but that's for another article.

Here at Tangent Labs we've come startlingly close to standardising the service layer. Django doesn't by default provide any implementation of a “service” – which would look something like:

from django.services import HttpService, ModelService
from django.conf import settings
from apps.document.models import Document

class DocumentService(ModelService, HttpService):
    self.model = Document
    self.host = settings.DOCUMENT_HOST

And indeed why would it? The notion, once you give it a moment's thought, is far too nebulous to be obviously reducible to generic base classes the way that Views and Models are – though perhaps this will not forever be the case. In the meantime: nothing is stopping us writing these ourselves.

Inside each app, between views.py and models.py, we have services.py (if that grows too large we can get more specific – it's not uncommon in Tangent projects to see backends.py, facades.py, apis.py, etc.) – this module will define a number of classes according to responsibility, according to the possibility for change, for being swapped out. So we might for example see something like:

class DocumentService(object):
    def __init__(self, retriever=None):
        if retriever is None:
            if settings.DEBUG == True:
                retriever = MockService()
            else:
                retriever = RestService()
        self.retriever = retriever

    def get_document(self, uuid):
        data = self.retriever.get(Document, uuid)
        return Document(**data)

class RestService(object):
    MAPPER = {Document: 'document'}
    URL = 'https://example.com/api/%(model)s/%(identifier)s/'

    def get(classinfo, identifier):
        params = {
            'model': self.MAPPER[classinfo],
            'identifier': identifier,
        }
        data = requests.get(self.URL % params).content
        return json.loads(data)

The service layer is designed to answer an inevitable question: ideally a Model should know nothing about anything beyond itself – at its farthest reach it might know about its immediately related models. Perhaps most importantly of all it should know nothing about how its object data will be processed, rearranged, presented – or indeed, on the other side, even how it will be stored. Meanwhile, however, neither should the View be merely by default the place to put the logic – a View has a well-defined responsibility, and for the purposes of testing, if for nothing else, it should be as thin as possible. Where, then, is the right place to put the logic? Which module has this responsibility?

One elegant solution (which is indeed employed by Django itself) is to use “Mixins” – a notion which is often used to mean not much more or less than “Abstract Views” – though of course there's no reason it needs to be so, if we wanted, for example, to extend a mixin from a management command. However, at a certain level of complexity, munging interfaces together with multiple inheritance takes its toll on modularity. This is the power of a proper service layer.

Of course, a service layer needn't be a single layer – in fact the great strength of the approach is that the interfaces between Models, Views, and then all the other services that make a website (database, cache, REST APIs, flat file dumps, etc.) can be as fat – or indeed, even better, as laminated (multiple thin layers) – as they need to be to maintain the ability to switch out modules based on change of responsibility. At least in theory the switch from, say, MongoDB to Solr (for some kind of document store) should be invisible to models and to views – this, of course, is the deep meaning of the notion (also employed by Django at core) of a “Backend.”

It's with this in mind that Tangent developed (long before we started doing Django) our first proper service layer, called IoC (short for “Inversion of Control”), implemented in our PHP library Taobase. Roughly, the module uses a service locator pattern with dependency injection to abstract interfaces away from module code. Inside some module method a service locator singleton is invoked whose sole responsibility is to attempt to find a given key (say “document.service”) in an XML config, where the said key would be attached to a class name and args to its constructor. Switching out a module becomes a matter of config. It looks something like this:

<services>
    <service name=”db” classname=”app_backends_db_Mysql” environment=”dev”>
        <constructor>
            <arg>localhost</arg>
            <arg>app-dev-rw</arg>
            <arg>secretpassword</arg>
            <arg>app_dev</arg>
        </constructor>
    </service>

    <service name=”document.retriever” classname=”app_backends_http_Rest” environment=”dev”>
        <constructor>
            <arg>localhost</arg>
            <arg>9999</arg>
            <arg>/document/</arg>
        </constructor>
    </service>

    <service name=”document.service” classname=”app_document_Service”>
        <constructor>
            <arg type=”service”>db</arg>
        </constructor>
    </service>
</services>

Then, from literally anywhere you need to access “document” data (in the browser, on the command line, in an AJAX call, over a message queue, whatever) you can do something like the following:

$doc_service = ioc_Locator::getService('document.service');
$doc_retriever = ioc_Locator::getService('document.retriever');
$doc_service->setRetriever($doc_retriever);

This is the sort of instantiation pattern you'll see all over Tangent's PHP code. The result is lots of thin modules with well-defined – unit-testable and (to some extent) correctness-provable – contracts, which know nothing about each other bar method interfaces (which can be made explicit in PHP with the interface and implements keywords – a bit of syntax I personally prefer to Python's redundant negative NotImplementedError).

As the years wear on into their fascinating future we developers are being required to think more and more in parallel (and distributed), which is a good thing because of course nature is not serial. The great power of Object Oriented (and indeed Functional) programming is that it speaks a kind of parallelism from the ground up, which we know as “modularity.” Moreover, with the flourishing of strong APIs and Cloud tech the idea of separate modules residing on the same machine or even on the same network is out the window. The same language, more and more, resides on both the client and the server side - GWT and Node.js/Meteor are the biggest examples but there are other manifestations of the sentiment including Jython and Silverlight – and of course, lest we forget, Java has always been both a client- and server-side language. Decoupling is more important than it's ever been.

In the Javascript, Python and PHP worlds alike MVC is in a strange place. Responsibilities are shifting as fast as the tech is arising – and it's coming thick and fast. As developers we all have a say in how our practice should be honed, and we hone by way of practice. Being collaborators as we are, we're sure that Tangent isn't the only one honing some kind of MVCS hybrid.

Share

Add a comment