Hosted by Three Crickets

Prudence
Scalable REST Platform
For the JVM

Prudence logo: bullfinch in flight

Model-View-Controller (MVC)

The source code for the examples in this chapter is available for download.

Background

The model-view-controller (MVC) family of architectural patterns has had great influence over user-interface programming and even design. At its core is the idea that the "model" (the data) and the "view" (the user interface) should be decoupled and isolated. This essentially good idea allows each layer to be optimized and tested on its own. It also allows the secondary benefit of easier refactoring in the future, in case one of the layers needs to be replaced with a different technology, a not uncommon requirement.
Forms, Forms, Forms
The problem is that you need an intermediary: a middle layer. For this reason, "classic" MVC, based around a thick controller layer, isn't as popular as it used to be: the controller does as much as possible in order to automate the development of user interfaces for very large applications that require constant maintenance and tweaking. This kind of MVC thus handles form validation, binding of form fields to database columns, and even form flows.
Forms, forms, as far as the eye can see. MVC was, and still is, the domain of "enterprise" user interfaces. It's telling that the manipulation of the data model is called "business logic": the use case for MVC is big business for big businesses. At its best, MVC makes hundreds of forms easier to maintain in the long run. At its worst, programmers drown in an ocean of controller configuration files, fighting against opaque layers of APIs that can only do what they were programmed to do, but not necessarily what is needed for a sensible UI.
Outside of the big business world, UI implementations use more flexible derivatives of MVC, such as Model-View-Presenter (MVP). The "presenter" is not an opaque layer, but rather is implemented directly by the programmer in code, often by inheriting classes that provide the basic functionality while allowing for customization. Depending on the variation you're using, the "business logic" might even be in the presenter rather than the model. Still, implementing MVP, like MVC, often comes from the same anxiety about mixing model and view.
MVC and the Web
These two do not seem an obvious match. The web is RESTful, such that the user interface (the "view") is no different from data (the "model"): both are RESTful resources, implemented similarly. In other words, in REST the model is the view.
Well, that's only really and entirely true for the "classic" web. Using JavaScript and other in-browser plugins, we get a "rich" web that acts no differently from desktop applications. The backend remains RESTful, essentially the "model," with controllers/presenters as well as views implemented entirely client-side. You can do full-blown, conventional MVC with the "rich" web.
MVC, however, has found inroads into the classic web: there exist many frameworks that treat web pages as a pure "view," an approach they go so far as to enforce by allowing you to embed only limited templating code into your HTML. Some of these frameworks even allow you to configure the form flow, which they then use to generate an opaque URI-space for you, and can even sabotage the browser "back" button to enforce that flow. (MVC automation at its finest! Or worst…)
The impetus for these brutally extreme measures is similar to the one with which we started: a desire to decouple the user interface from everything else. HTML is the realm of web designers, not programmers, and mixing the work of both professions into a single file presents project management challenges. However, there's a productive distance between cleaning up HTML pages and full-blown MVC, which unfortunately not enough frameworks explore. And actually, not everything called "MVC" really is MVC.
So what are we left with in Prudence? As you'll see, Prudence supports a straightforward MVP architecture while still adhering to RESTful principles. Read on, and consider if it would benefit your project to use it. You do not have to. Our recommendation is to use what works best for you and your development team.

Tutorial

Models

You should implement this layer as is appropriate to the database technology and schema you are using. Object-oriented architecture are common, but of course not necessary. The model layer as a whole should live in your "/libraries/" subdirectory. For this example, let's put it under "/libraries/models/".
Do you want the "business logic" to live in the model layer? If so, your classes should be of a somewhat higher level of abstraction above the actual data structure. If you prefer the models to more directly represent the data, then you have the option of putting the "business logic" in your presenters instead.
For our example, let's implement a simple in-memory model, as "/libraries/models/user.js":
var Models = Models || {}
​
/**
 * Retrieve a person model from the database.
 */
Models.getPerson = function(name) {
	var person = new Models.Person()
	person.setUsername(name)
	return person
}
​
Models.Person = function() {
	this.getUsername = function() {
		return this.username
	}
​
	this.setUsername = function(username) {
		this.username = username
	}
​
	this.getComments = function() {
		return this.comments
	}
​
	this.comments = new Models.Messages()
}
​
Models.Messages = function() {
	this.get = function() {
		return this.messages
	}
​
	this.add = function(message) {
		this.messages.append(message)
	}
​
	this.messages = ['This is a test.', 'This is also a test.']
}

Views

In Prudence, these are hidden template resources. For this example, let's put them under "/libraries/includes/views/".
If you prefer to use templating languages for your views, Velocity and Succinct are supported. Your designers may also find it useful to use the supported HTML markup languages. Even if you prefer templating, you can still "drop down" to dynamic languages, such as JavaScript (server-side), when useful: Prudence allows you to easily mix and match scriplets in different languages. If you do so, take special note of the nifty in-flow tag.
There are some who shudder at the thought of mixing dynamic languages and HTML. This likely comes from bad experience with poorly-designed PHP/JSP/ASP applications, where everything gets mixed together into the "view" file. If you're afraid of losing control, then you can simply make yourself a rule that only templating languages are allowed in template resources. It's purely a matter of project management discipline. We recommend, however, relaxing some of that extremism: for example, you can make the rule that no "business logic" should appear together with HTML, while still allowing some flexibility for using server-side JavaScript, but only for UI-related work. Still unconvinced? We'll show you below how to use your favorite pure templating engine with Prudence.
The required data will be injected into the view by the presenter as an "object" POST payload, available via the conversation.entity API. We'll detail below how that happens. For now, here's our example view, "/libraries/includes/views/user/comments.html":
<html>
<%
var context = conversation.entity.object
var person = context.person
var comments = person.getComments().get()
%>
<body>
	<p>These are the comments for user: <%= person.getUsername() %></p>
	<table>
<% for (var c in comments) { %>
		<tr><td><%= comments[c] %></td></tr>
<% } %>
	</table>
	<p>You may add a comment here:</p>
	<form>
		<input name="comment" />
		<input type="submit" />
	</form>
</body>
</html>

Presenters

In Prudence, these are the resources that are actually exposed in the URI-space, while the views remain hidden. The presenter retrieves the appropriate view and presents it to the user.
You can use either manual or template resources as your presenters. However, manual resources offer a bit more flexibility, so we will choose them for our example. Our example presenter is in "/resources/user/comments.m.js":
document.require(
	'/models/user/',
	'/prudence/resources/',
	'/sincerity/objects/')
​
function handleInit(conversation) {
	conversation.addMediaTypeByName('text/html')
}
​
function handleGet(conversation) {
	var name = conversation.locals.get('name')
	var person = Models.getPerson(name)
	return getView('user/comments', {person: person})
}
​
function handlePost(conversation) {
	var name = conversation.locals.get('name')
	var comment = conversation.form.get('comment')
	var person = Models.getPerson(name)
	person.getComments().add(comment)
	return getView('user/comments', {person: person})
}
​
function getView(view, context) {
	var page = Prudence.Resources.request({
		uri: '/views/' + view + '/',
		internal: true,
		method: 'post',
		mediaType: 'text/*',
		payload: {
			type: 'object',
			value: context
		}
	})
	return Sincerity.Objects.exists(page) ? page : 404
}
To keep the example succinct, we're only making use of a single view in this presenter, though it should be clear that you can use any appropriate logic here to retrieve any view using getView.
getView is where the MVC "magic" happens, but as you can see it's really nothing more than an internal request. We're specifically using two special features of internal requests:
We'll remind you also that internal requests are fast. They emphatically do not use HTTP, and "object"-type payloads are not serialized.
Here's our routing.js entry for the presenter:
app.routes = {
	...
	'/user/{name}/comments/': '/user/comments/!'
}
Note the use of capture-and-hide.
Voila. Test your new MVC application by pointing your web browser to "/user/Linus/comments/" under your application's base URL.

Implications for Caching

You have two options for implementing caching:

View Templates

One size does not fit all. Almost every web framework comes with its own solution to templating, with its own idiosyncratic syntax and set of features, manifesting its own templating philosophy. As you've probably picked up, Prudence's philosophy is that the programmer knows best: scriptlets should be able do anything, and the programmer doesn't need to be "protected" from bad decisions via a dumbed-down, sandboxed templating domain language.
There are two common counter-arguments, which we don't think are very convincing.
The first is that the people designing the templates might not, in fact, know best: they might not be proficient programmers, but instead web designers who specialize in HTML/CSS coding and testing. They would be able to deal with a few inserted template codes, but not a full-blown programming language. The "real" programmers would be writing the controllers/presenters, and injecting values into the templates according to the web designers' needs. This argument carries less validity than it used to: proficient web designers these days need to know JavaScript, and if they can handle client-side JavaScript, they should be able to handle server-side JavaScript, too. Will they need to learn some new things? Yes, but learning a new templating language is no trivial task, either.
The second counter-argument is about discipline: even competent programmers might be tempted to make "shortcuts," and insert "business logic" into what should be purely a "view." This would short-circuit the MVC separation and create hard-to-manage "spaghetti" code. A restricted templating language could, then, enforce this discipline. This seems like a brutal solution: programmers get annoyed if their own platforms don't trust them, and in any case can circumvent these restrictions by writing plugins that would then do what they need. But the real issue is that discipline should be handled as a social issue of project management, not by tools.
In any case, we won't force our philosophy on you: Prudence has built in support for two templating engines, and it's easy to plug in a wide range of alternative templating engines into Prudence. If you're familiar and comfortable with a particular one, use it. We'll guide you in this section.
There are many templating engines you can use. The best performing and most minimal solutions are pure JVM libraries: StringTemplate, Thymeleaf, Snippetory and Chunk. However, popular ones use other languages, for example Jinja2 for Python. Below are examples per both types.
The technique we'll show for using both types is the same: writing a custom dispatcher, so make sure you understand dispatching before you continue to read.

StringTemplate Example

For this example, we chose StringTemplate: it's very minimal, and stringently espouses a philosophy entirely opposite to Prudence's: proof that Prudence is not forcing you into a paradigm! We'll of course use the Java/JVM port, though note that StringTemplate is also ported to other languages.
It's available on Maven Central, so you can install it in your container using Sincerity:
sincerity attach maven-central : add org.antlr ST4 : install
Otherwise, you can also download the binary Jar from the StringTemplate site and place it in your container's "/libraries/jars/" directory.
Here's our application's "/libraries/dispatchers/st.js":
document.require('/sincerity/objects/')
​
function handleInit(conversation) {
	conversation.addMediaTypeByName('text/html')
}
​
function handlePost(conversation) {
	if (!conversation.internal) {
		return 404
	}
	var id = String(conversation.locals.get('com.threecrickets.prudence.dispatcher.id'))
	if (id.endsWith('/')) {
		id = id.substring(0, id.length - 1)
	}
	var st = getDir().getInstanceOf(id)
	if (!Sincerity.Objects.exists(st)) {
		return 404
	}
	if (Sincerity.Objects.exists(conversation.entity)) {
		var context = conversation.entity.object
		if (Sincerity.Objects.exists(conversation.context)) {
			for (var key in context) {
				var value = context[key]
				if (Sincerity.Objects.isArray(value)) {
					for (var v in value) {
						st.add(key, value[v])
					}
				}
				else {
					st.add(key, value)
				}
			}
		}
	}
	return st.render()
}
​
function getDir() {
	var dir = new org.stringtemplate.v4.STRawGroupDir(application.root + '/libraries/views/')
	dir.delimiterStartChar = '$'
	dir.delimiterStopChar = '$'
	return dir
}
The StringTemplate API is very straightforward and this code should be easy to follow. Notes:
Now for our routing.js:
app.routes = {
	...
	'/views/*': '@st:{rw}'
}
​
app.dispatchers = {
	...
	st: {
		dispatcher: '/dispatchers/st/'
	}
}
See how we've interpolated the wildcard into "{rw}": this means that a URI such as "/views/hello/" would translate to the ID "hello".
Let's create our template, "/libraries/views/user/comments.st":
<html>
<body>
	<p>These are the comments for user: $username$</p>
	<table>
		$comments:{c | <tr><td>$c$</td></tr>}$
	</table>
</body>
</html>
Note the use of an anonymous template (a lambda). As an alternative, we can also use named templates, which we can to group into reusable libraries. This is easy to do with group files. With that, here's an alternative definition of the above, saved as "/libraries/views/user.stg":
comments(username, comments) ::= <<
<html>
<body>
	<p>These are the comments for user: $username$</p>
	<table>
		$comments:row()$
	</table>
</body>
</html>
>>
​
row(content) ::= <<
<tr><td>$content$</td></tr>
>>
Note that STRawGroupDir treats these ".stg" files as if they were a directory with multiple files when you look up an ID, so the URI to access "comments" would be the same in both cases: "/views/users/comments/".
Finally, our presenters would work the same as in the MVC tutorial. The only change would be to flatten out the contexts for StringTemplate to use:
var person = Models.getPerson(name)
...
return getView('user/comments', {
	username: person.getUsername(),
	comments: person.getComments().get()
})
That's it!
Implications for Caching
You can set caching on your presenter, but unfortunately you can't set different caching parameters per view. StringTemplate's brutal rejection of any kind of programming logic in templates means that you can't "call" anything from within a template, not even to change a parameter.
In our next example, we'll be using a more flexible engine that allows for more integration with Prudence features.

Jinja2 Example

Jinja2 is an embeddable engine that mimics the well-known template syntax of the Django framework. We'll go over its basic integration into Prudence, and also show you how to write Jinja2 custom tags to easily take advantage of Prudence's caching mechanism.
First we need to install Python and Jinja2 in our container:
sincerity add python : install : easy_install Jinja2==2.6 simplejson
We're also installing the simplejson library, because Jython doesn't come with the JSON support we'll need (more on that later).
Note that Jinja2 version 2.7 doesn't work in Jython (might be fixed for version 2.8), but version 2.6 does, so that's what we use here.
For our dispatcher, we'll do something a bit different from before: because we want to support caching of templates, we will want the actual template renderer as a template resource. The dispatcher, then, will simply delegate to that template resource. Another change is that we'll be writing it all in Python, so we can call the Jinja2 API. Let's start with "/libraries/dispatchers/jinja.py":
import simplejson, urllib
from com.threecrickets.prudence.util import InternalRepresentation
​
def handle_init(conversation):
    conversation.addMediaTypeByName('text/html')
​
def handle_post(conversation):
    if not conversation.internal:
        return 404
    id = conversation.locals['com.threecrickets.prudence.dispatcher.id']
    if id[-1] == '/':
        id = id[0:-1]
    id += '.html'
    context = {}
    if conversation.entity:
        if conversation.entity.mediaType.name == 'application/internal':
            context = conversation.entity.object
        else:
            context = conversation.entity.text
            if context:
                context = simplejson.loads(context)
    payload = InternalRepresentation({
        'context': context,
        'uri': str(conversation.reference),
        'base_uri': str(conversation.reference.baseRef)})
    resource = document.internal('/jinja-template/' + urllib.quote(id, '') + '/', 'text/html')
    result = resource.post(payload)
    if not result:
        return 404
    return result.text
Notes:
Our template resource is "/resources/jinja-template.t.html":
<%python
import urllib
from jinja2 import Environment, FileSystemLoader
from jinja2.exceptions import TemplateNotFound
from os import sep
​
id = urllib.unquote(conversation.locals['id'])
payload = conversation.entity.object
context = payload['context']
​
loader = application.globals['jinaj2.loader']
if not loader:
    loader = FileSystemLoader(application.root.path + sep + 'libraries' + sep + 'views')
    loader = application.getGlobal('jinja2.loader', loader)
env = Environment(loader=loader)
​
try:
    template = env.get_template(id)
    print template.render(context)
except TemplateNotFound:
    conversation.statusCode = 404
%>
The Jinja2 API is very straightforward and this code should be easy to follow. Note that we're caching the FileSystemLoader as an application.global: because it can pick up our changes on-the-fly and cache them, it's eminently reusable.
Now for our routing.js:
app.routes = {
	...
	'/views/*': '@jinja:{rw}',
	'/jinja-template/{id}/': '/jinja-template/!'
}
​
app.dispatchers = {
	...
	jinja: {
		dispatcher: '/dispatchers/jinja/'
	}
}
See how we've interpolated the wildcard into "{rw}": this means that a URI such as "/views/hello/" would translate to the ID "hello/".
Let's create our template, "/libraries/views/user/comments.html":
<html>
<body>
	<p>These are the comments for user: {{username}}</p>
	<table>
{% for comment in comments %}
		<tr><td>{{comment}}</td></tr>
{% endfor %}
	</table>
</body>
</html>
Finally, we need to make two changes to our presenter. First, we need to isend JSON payloads (if we were writing it in Python, we could optimize by sending "object" payloads):
return getView('user/comments', {
	username: person.getUsername(),
	comments: person.getComments().get()
})
​
function getView(view, context) {
	var page = Prudence.Resources.request({
		uri: '/views/' + view + '/',
		internal: true,
		method: 'post',
		mediaType: 'text/*',
		payload: {
			type: 'json',
			value: context
		}
	})
	return Sincerity.Objects.exists(page) ? page : 404
}
And we have to "flatten" the model in order to make JSON-able:
var person = Models.getPerson(name)
...
return getView('user/comments', {
	username: person.getUsername(),
	comments: person.getComments().get()
})
That's it!
Custom Tags
It's fairly easy to add custom tags to Jinja2. Let's add some to support Prudence caching, as well as other useful Prudence values. Here's "/libraries/jinja_extensions.py":
from jinja2 import nodes
from jinja2.ext import Extension
from org.restlet.data import Reference
​
class Prudence(Extension):
    # a set of names that trigger the extension
    tags = set(['current_uri', 'application_uri', 'to_base', 'cache_duration', 'cache_tags'])
​
    def __init__(self, environment):
        super(Prudence, self).__init__(environment)
​
        # add the defaults to the environment
        environment.extend(
            prudence_caching=None,
            prudence_uri=None,
            prudence_base_uri=None
        )
​
    def parse(self, parser):
        token = parser.stream.next()
        tag = token.value
        lineno = token.lineno
        
        if tag == 'current_uri':
            return _literal(self.environment.prudence_uri, lineno)
​
        elif tag == 'application_uri':
            return _literal(self.environment.prudence_base_uri, lineno)
​
        elif tag == 'to_base':
            base = Reference(self.environment.prudence_base_uri)
            reference = Reference(base, self.environment.prudence_uri)
            
            # reverse relative path to the base
            relative = base.getRelativeRef(reference).path
            
            return _literal(relative, lineno)
​
        elif tag == 'cache_duration':
            duration = parser.parse_expression().as_const()
            self.environment.prudence_caching.duration = duration
​
        elif tag == 'cache_tags':
            tags = [parser.parse_expression().as_const()]
            while parser.stream.skip_if('comma'):
                tags.append(parser.parse_expression().as_const())
​
            cache_tags = self.environment.prudence_caching.tags
            for tag in tags:
                cache_tags.add(tag)
            
        return _literal('', lineno)
​
def _print(text, lineno):
    return nodes.Output([nodes.TemplateData(text)]).set_lineno(lineno)
We'll then modify our "/resources/jinja-template.t.html" to use our extension, and set it up using the attributes forwarded from the dispatcher.:
env = Environment(loader=loader, extensions=['jinja_extensions.Prudence'])
env.prudence_caching = caching
env.prudence_uri = payload['uri']
env.prudence_base_uri = payload['base_uri']
Here's a simple template to test the extensions:
<html>
{% cache_duration 5000 %}
{% cache_tags 'tag1', 'tag2' %}
<body>
<p>This page is cached for 5 seconds.</p>
<p><b>current_uri</b>: {% current_uri %}</p>
<p><b>application_uri</b>: {% application_uri %}</p>
<p><b>to_base</b>: {% to_base %}</p>
</body>
</html>

RESTful Models

In our MVC tutorial above, we've implemented our models as classes (OOP). However, it may make sense to implement them as RESTful resources instead.
Doing so allows for powerful deployment flexibility: it would be possible to decouple the model layer entirely, over HTTP. For example, you could have "model servers" running at one data center, close to the database servers, while "presentation servers" run elsewhere, providing the direct responses to users. In this scenario, the presenters would be calling the models using secured HTTP requests, instead of function calls.
Even if you're not planning for such flexibility at the moment, it might still be a good idea to allow for it in the future. Until then, you could optimize by treating the model layer as an internal API, which makes it about as fast as function calls.
There are two potential downsides to a RESTful model layer. First, there's the added programming complexity: it's easier to create a class than a resource. Second, RESTful resources are limited to four verbs: though GET/POST/PUT/DELETE might be enough for most CRUD operations, it can prove harder to design a RESTful URI-space for complex "business logic."
A good compromise, if necessary, can be to still use HTTP to access models, just not RESTfully: use Remote Procedure Call (RPC) instead. We discuss this option in the URI-space architecture tips.

Tutorial

For simplicity, we'll use a mapped resource, "/resources/models/person.m.js":
document.require(
	'/models/user/',
	'/sincerity/json')
​
function handleInit(conversation) {
	conversation.addMediaTypeByName('application/json')
	if (conversation.internal) {
		conversation.addMediaTypeByName('application/internal')
	}
}
​
function handleGet(conversation) {
	var name = conversation.locals.get('name')
	var person = Models.getPerson(name)
	var result = {
		username: person.getUsername(),
		comments: person.getComments().get()
	}
	return conversation.mediaTypeName == 'application/internal' ? result : Sincerity.JSON.to(result)
}
​
function handlePost(conversation) {
	var name = conversation.locals.get('name')
	var payload = Sincerity.JSON.from(conversation.entity.text)
	if (payload.comment) {
		var person = Models.getPerson(name)
		person.getComments().add(comment)
		var result = {
			username: person.getUsername(),
			comments: person.getComments().get()
		}
		return conversation.mediaTypeName == 'application/internal' ? result : Sincerity.JSON.to(result)
	}
	return 400
}
We would then modify our presenter like so:
function handleGet(conversation) {
	var name = conversation.locals.get('name')
	var person = getModel('person/' + encodeURIComponent(name))
	return getView('comments', {person: person})
}
​
function handlePost(conversation) {
	var name = conversation.locals.get('name')
	var comment = conversation.form.get('comment')
	var person = postModel('person/' + encodeURIComponent(name), {comment: comment})
	return getView('comments', {person: person})
}
​
function getModel(model) {
	return Prudence.Resources.request({
		uri: '/models/' + model + '/',
		internal: true
	})
}
​
function postModel(model, payload) {
	return Prudence.Resources.request({
		uri: '/models/' + model + '/',
		internal: true,
		method: 'post'
		payload: {
			type: 'object',
			value: payload
		}
	})
}
We've assumed an internal request here, but it's easy to change it to an external request if the model layer runs elsewhere on the network.
Finally, here's our addition to routing.js, using capture-and-hide:
app.routes = {
	...
	'/models/person/{name}/': '/models/person/!'
}

The Prudence Manual is provided for you under the terms of the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. The complete manual is available for download as a PDF.

Download manual as PDF Creative Commons License