Getting Started With Pintura

SitePen Blog • by Kris Zyp

Pintura is a REST-style web framework that utilizes a layered approach to application development that facilitates straightforward, well-designed rich internet applications. Pintura forms the core web framework for Persevere 2.0, and consists of a powerful data modeling and persistence framework called Perstore, in combination with a REST-style web framework. You can read more about the goals and principles behind Pintura, but here we will look at how to get started writing applications.

Pintura-based applications normally consist of server-side data models with three layers: data stores, store models, and model facets. On top of this, different representation handlers (for serializing data to different formats) can be defined, but Pintura comes with a good set of these ( including JSON, JavaScript, multipart, and Atom), so usually that is not necessary. This provides a well-structured separation of concerns, distinguishing storage configuration (data stores), core data logic (models), varying capabilities of access to the data (facets), and data serialization (representations). Perhaps the easiest way to understand this approach to take a look at an example application.

Naturally we need to install Pintura first. Pintura is JavaScript based and can run on various CommonJS JavaScript servers including Node and Narwhal/Jack, and the various engines they can run on. The Pintura installation process is described here.

A basic Pintura application is very easy to create. If we start by making a copy of /template/, we can create an application with a server-side model class by simply adding this to the lib/app.js:

var Model = require("model").Model;
Model("MyData", null, {
});

Once we start up our server for this application, we now have the full Persevere HTTP/REST interface for creating, querying, updating, and deleting objects from the MyTable model. We have everything we need on the server to write a web client application, which can utilize REST methods like GET, PUT, POST, and DELETE, (as well as JSON-RPC) to interact with the server model class to store and query data. We get an enormous amount of functionality for free, including cross-site capabilities (JSONP, window.name, and CORS support), cross-site request forgery protection, authentication, conditional request handling, and more.

However, most applications will require more server-side logic than simply a generic data store. Generally, application code will be written as model and facet definitions using Pintura’s persistence framework (Perstore). With this framework, we can define handlers for different aspects of the object’s interaction and lifecycle, such as get, put, and delete handlers. We can define data integrity contracts by adding JSON schema constraints in basically the same way as in Persevere 1.0 (except our model classes are defined with the Model constructor from the model module rather than a top-level Class constructor, to be more inline with CommonJS and standard web framework terminology). We can define additional methods for the model as well as methods for the persisted object instances (by defining methods on the model prototype), once again using the same structure as the first version of Persevere. For example, we could easily add a new method on object instances:

var Model = require("model").Model;
Model("MyData", null, {
prototype: {
addFriend: function(friend){
this.friends.push(friend);
this.save();
}
}
});

Pintura comes with an example wiki, we will examine how it is constructed to see how we can further understand what it looks like to build an application with Pintura. The example wiki is found in the example folder in the Pintura distribution, and we will break out the various parts and how they fit together.

We will start by looking at the app.js file found in the example/lib directory. This is the entry module which loads the other modules, starting from a user perspective, first defining the application-specific resource representations and user access levels. The example/lib/access.js module defines a set of facets for accessing data, for the different sets of users. For each user, a set of facets is returned and the facet provides the capability through which the user can access the application data. We can then drill down into example/lib/facet/page.js module, which defines how the underlying model class can be accessed through each facet. The example/lib/model/page.js defines the core application logic which in turn accesses the data store that provides the low-level interaction with the underlying storage medium.

Let’s look more closely at each of the levels, starting at the bottom with the store. A data store is the basic entity which allows for RESTful interaction with a storage system. Storage systems can be relational SQL-based databases, document based databases, OS files, remote servers, and so on. The object store API follows the W3C Indexed Database API for an object store. Pintura’s Perstore framework comes with a variety of data store implementations, including a SQL datastore for Rhino, and a JavaScript/JSON file-based data store. Creating your own store is not difficult, it essentially involves implementing get(id), put(object, id), query(query), and delete(id) functions (and optionally create, transaction, and subscribe for additional functionality). In our example application, we don’t define any special stores, we will simply use the default store, which out of the box is the js-file module, an object store which saves records into a JavaScript/JSON file.

The object store API is not only used at the bottom storage level in Pintura, but the model class and facets also follow the same API for consistency; classes and facets are simply wrappers that expose the same store API, adding their own functionality and/or constraining operations. Model classes act as the gateway to stores, providing the central core logic of the application. Any application data logic that should be enforced for all users should be implemented here. Models do not need to implement the entire store API themselves. Any store method that is implemented simply passes through the underlying object store. An empty class is perfectly valid, and would result in all REST actions being passed through to the object store. In example/lib/model/page.js, we can see the put handler is implemented to add a lastModifiedBy property and create a new PageChange instance:

exports.Page = Model("Page", pageStore, {
put: function(object, id){
object.lastModifiedBy = auth.currentUser.username;

We can also define the prototype for this class, which allows us to create methods that are available on all instances of the class.

PinturaArchitecture

At the next layer up, we have the set of facets used to access the Page class. Facets are defined in a similar way to model classes, they follow the store API, wrapping a class, and only methods with specific logic need to be implemented. There are two primary types of facets, restrictive and permissive facets. These differ in how they handle unimplemented methods. A permissive facet will default to passing all actions through to the underlying class. A restrictive facet defaults to only passing retrieval actions (get and query) through, methods have to be explicitly implemented to interact with the class.

In example/lib/facet/page.js we see three facets defined to accommodate different groups of users. The public facet is defined to be restrictive, essentially making the objects read-only for public users. The public facet also defines the query method so that only published pages are accessible. The user facet and admin facet are permissive, allowing all actions on the pages. All of these facets control access to the model class, and in fact, model classes are simply a special permissive facet that applies to data stores.

Continuing back up the stack, the access module can now be understood as the initial logic that determines which facets are available for the authenticated user, so their access is properly controlled.

The initial example/lib/app.js file also defines the custom media module used for generating the HTML for pages. Pintura comes with a good selection of media handlers out of the box; the JavaScript and JSON handlers are the primary use case handlers, allowing objects to easily be accessed through XHR by Ajax applications. Pintura is designed first and foremost for Ajax applications, so normally these will be used most frequently. However, we need a specific HTML handler in this case so that pages can be directly generated as HTML for easy indexing by search engines.

Media handlers are simple to implement. In the example/lib/media/wiki-html.js handler we can see that we just need to define the media type, the quality of the media type (for content negotiation), and the serializer. Bi-direction media handlers can also implement a deserialize method (for handling PUT and POST requests), but this isn’t needed for our Wiki, all updates to pages will be done through JSON-based XHR requests. The Wiki HTML handler simply uses the the wiky package to do the transformation of markup to HTML for us.

By default, Pintura does all the HTTP request handling for you, handling cross-site issues, authentication, transaction management, and converting requests to calls to facets and model classes. However, Pintura is highly modular, actually consisting of about 13 modular middleware appliances, many of which can be used on their own. With these set of modules, one can easily create new HTTP request handling stacks, or reuse individual modules for handling other requests. To see the Pintura stack, you can open up lib/pintura.js, and easily see the different middleware modules that come together in Pintura (this file also lists the set of media type handlers that Pintura registers by default).

For an application running on Jack, the Pintura application stack is defined as the request handler in jackconfig.js (in Node, it is defined in start-node.js). With these startup files we can define our own additional request handling logic, and still defer store related requests to the Pintura handler. This application definition defines static file handlers to simplify development. For the example application, we also created a very simple JSGI middleware module redirect-root that will check for requests to the root path, and redirect to a specific “root” wiki page. This middleware wraps the pintura.app request handler, handling the root path, and delegating all other requests to Pintura.

The example wiki gives us a good starting point for beginning to build with Pintura. Much of the persistence, model and facet APIs are defined at the Perstore project page. In the next article in this series, we will look at some more examples of how to leverage the persistence framework and its various stores.

Pintura provides the complete web client-server REST functionality of Persevere combined with a simple, modular, JavaScript-based, facet-oriented architecture, for easy to build to JavaScript applications, with end-to-end consistency, and flexible control and security.

Related posts:

  1. Introducing Pintura
  2. CommonJS/JSGI: The Emerging JavaScript Application Server Platform
  3. Getting Started with Persevere Using Dojo
About these ads
Cette entrée a été publiée dans Uncategorized. Bookmarquez ce permalien.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s