 | Level: Intermediate Dan Jemiolo (danjemiolo@us.ibm.com), Advisory Software Engineer, IBM
16 Oct 2007 One of the main goals of Project Zero is to simplify the creation
of rich Internet applications (RIAs). The Flickr photo sharing service is an
excellent example of such an application. Designed using REST principles, Asynchronous JavaScript + XML (Ajax) techniques, and dynamic scripting languages, Flickr provides a service that is not only user friendly, but also scalable and extensible. Because Flickr has many of the qualities that authors of other RIAs are striving for, recreating this type of application with Zero would be an excellent way to validate Zero as an RIA platform. In this article, see how to combine existing Zero components to create a photo sharing service that can support many of the same functions provided by Flickr today. Along the way, you'll learn more about RESTful design, connecting components via HTTP, and the use of JavaScript to provide a function that isn't already part of Zero.
Before you get started
This article assumes that you have downloaded Project Zero and either completed the introductory tutorial or written a simple application yourself. You should be familiar with the basic principles of REST and the different types of HTTP methods (GET, POST, etc.). See the Resources section for an introduction to REST.
Introduction to photo sharing application design
Yahoo!'s Flickr photo sharing service (see Resources) is
an excellent example of an RIA. Because Flickr has many of the qualities that
authors of other RIAs are striving for, recreating such an application with Zero
validates that Zero is an excellent platform for developing RIAs. Fortunately, we
will not have to create an entire photo sharing service from scratch — the Zero platform includes a number of RIA components such as a blog, a rating system, a profile manager, and more, all of which you can reuse when building your own applications. The following sections discuss the nature of a photo sharing service and show that this type of application has a lot in common with the components that exist in Zero today.
 |
The Project Zero community
Take a stroll around the Project Zero Web site, and see how Project Zero provides a powerful — but radically simple — development and execution platform for modern Web applications. The active community discusses project development, provides help to developers, and wants to hear your ideas! |
|
Designing a photo sharing application
Creating a photo sharing service might seem like a massive undertaking at first,
but if you stop and think about it, you may see that this type of application has a lot in common with two well understood Web 2.0 applications: blogging and file sharing. Flickr is essentially a blogging service that provides a more domain-specific view of its blog posts. Each photo in a user's collection is represented by a blog post, and the photos (blog posts) are organized into albums (blog categories) and augmented with descriptive tags. Further, Flickr allows users to access their actual image files outside of its slick user interface, which implies that the image storage is handled by some high-end file sharing service that is separate from the blog service. Project Zero includes both blog and file share components in its suite of optional libraries, which can be tied together to form a similar photo sharing experience.
The implementation of our photo sharing UI will store photos
using the file share component and organize them using the blog component. While
users browsing the contents of the file shares or blogs will be able to see their
photos there as well, the photo sharing UI is much more photo-centric and user
friendly. It's beyond the scope of this article to cover how to create all of the
fancy Ajax or Flash-driven widgets that make the Flickr UI so impressive, but it does explain the RESTful interfaces and logic needed to drive the behavior of such widgets. With a well-designed photo sharing service in place, it should be possible to put any type of UI frills on top of it.
Let's take a closer look at the design of Zero's blog and file share components to see how easily they can fit into our photo sharing application.
Use RESTful resources as building blocks
Both the blog and file share components are made up of one or more resource types
that can be manipulated using HTTP methods. Each HTTP method represents a read or write operation on a resource, and each HTTP URI represents an individual resource instance. As an example, an HTTP GET against http://www.example.com/blogs/jsmith/posts/our-latest-vacation might return a representation of a blog post by user jsmith entitled Our Latest Vacation. Similarly, an HTTP POST against http://www.example.com/blogs/jsmith might be used to create a new blog post in user jsmith's blog. Table 1 explains how HTTP methods and URIs are used to interact with Zero's blog service, including the format of the data exchanged between client and server. The URI tokens in bold are variables:
Table 1. REST interface for the Zero blog component
| URI | HTTP Method | Data Format | Description |
|---|
| /blogs | POST | JSON | Create a new blog using the JSON representation sent in the request body | | /blogs/blog-name
| GET | JSON | Retrieve the resource definition for the blog with the given name | | /blogs/blog-name
| PUT | JSON | Update the resource definition for the blog with the given name | | /blogs/blog-name
| DELETE | None | Delete the blog with the given name, including all of its posts and comments | | | | | | /blogs/blog-name/posts | POST | Atom | Create a new blog post on the JSON representation sent in the request body | | /blogs/blog-name/posts/post-id
| GET | Atom | Retrieve the resource definition for the blog post with the given ID | | /blogs/blog-name/posts/post-id
| PUT | Atom | Update the entire resource definition for the blog post with the given ID | | /blogs/blog-name/posts/post-id
| DELETE | None | Delete the blog post with the given ID |
When you add in authentication and authorization through HTTP, you can see that this
API provides all of the function necessary to manage one or more blogs. The API
can be invoked by Ajax toolkits such as Dojo very easily, allowing developers to
integrate the response data with UI widgets without a page refresh (see Resources for a link to more information on the Dojo Toolkit). Of
course, the API can also be accessed by an ordinary HTTP client, such as Apache's
HttpClient (for Java™ technology) or the cURL command-line tool (for C)
(see Resources for links). You can even invoke parts of the API using your Web browser's address bar. Blogs are created and modified using the JavaScript Object Notation (JSON) while blog posts are manipulated with Atom (see Resources for links). Table 2 shows a similar API for the file share component, which stores public files and directories:
Table 2. REST interface for the Zero file share component
| URI | HTTP Method | Data Format | Description |
|---|
| /file-shares/user-name
| POST | JSON | Add a new file or directory to the user's file share using its JSON representation | | /file-uploads/user-name
| POST | HTML form data | Add a new file or directory to the user's file share using an HTML form | | /file-shares/user-name/path
| GET | JSON | Retrieve the file or directory at the given path in the user's file share | | /file-shares/user-name/path
| PUT | JSON | Update the file or directory at the given path in the user's file share | | /file-shares/user-name/path
| DELETE | None | Delete the file or directory at the given path in the user's file share |
Items of note in the file share API include the use of user names to split up the
shared directories and the duplicate ways to add a file. The fact that every user
has his or her own shared directory will make it easy to identify a place for
storing image files on behalf of his or her photo sharing account. The two methods
of adding a file through HTTP POST give you flexibility when it comes to designing
your photo album UI — you can either use HTML forms to upload the
data or you can use non-browser clients similar to Flickr's Uploadr tool (see Resources for a link).
With these APIs in hand, you can now design our photo sharing service as a set of
operations on the two components. You do not need to know any of the particulars about the component implementations — all interaction with them will be through their RESTful HTTP interfaces. The next step is to make some concrete decisions on how photo sharing actions map to HTTP requests.
Mapping photo sharing actions to blogs and file shares
You can start designing your application by constructing a table similar to Table 1 and Table 2 and seeing what function is
covered by the blog and file share components and what needs to be written. Table 3 provides English-only descriptions of the features you need to
support and how they would map to invocations of the component APIs:
Table 3. Mapping photo sharing actions to blogs and file shares
| Photo Sharing Action | What Happens Under the Covers |
|---|
| Create a new photo sharing account | Create a new blog to sort the photos Create a new file share directory for the image files
| | Create a new photo album | Create a new blog category
| | Add a photo to a photo album | Upload a copy of the image file to the file share directory Create a new blog post with an <img/> tag pointing to the image file
| | Retrieve all photos | Retrieve all blog posts in the photo blog
| | Retrieve an entire photo album | Retrieve all blog posts in a given category
| | Retrieve an individual photo | Retrieve the photo's blog post
| | Update an individual photo, including its album | Update the photo's blog post and category name
| | Delete all photos | Delete the photo blog Optionally delete the file share directory with the image files
| | Delete an entire photo album | Delete the blog category Delete the blog posts in the category Optionally delete the image files associated with the blog posts
| | Delete an individual photo | Delete the blog post Optionally delete the image file associated with the blog post
|
Table 3 tells you that only a few of your features require
multiple operations "under the covers," so the amount of glue code
you'll need to write will be minimal. Table 4 is just like Table 3 except that the English descriptions have been replaced with the exact HTTP methods and URIs that need to be used:
Table 4. Mapping photo sharing actions to HTTP requests
| Photo Sharing Action | What Happens Under the Covers |
|---|
| Create a new photo sharing account | HTTP POST to /blogs
HTTP POST to /file-shares/user-name
| | Create a new photo album | HTTP POST to /blogs/blog-name/categories
| | Add a photo to a photo album | HTTP POST to /files-shares/user-name
HTTP POST to /blogs/blog-name/posts
| | Retrieve all photos | HTTP GET on /blogs/blog-name
| | Retrieve an entire photo album | HTTP GET on /blogs/blog-name/categories/category-name
| | Retrieve an individual photo | HTTP GET on /blogs/blog-name/post/post-id
| | Update an individual photo, including its album | HTTP PUT to /blogs/blog-name/post/post-id
| | Delete all photos | HTTP DELETE on /blogs/blog-name
HTTP DELETE on /file-shares/user-name/photos
| | Delete an entire photo album | HTTP DELETE on /blogs/blog-name/categories/category-name
HTTP DELETE on /blogs/blog-name/posts/post-id
HTTP DELETE on /file-shares/user-name/photos/photo-file-name
| | Delete an individual photo | HTTP DELETE on /blogs/blog-name/posts/post-id
HTTP DELETE on /file-shares/user-name/photos/photo-file-name
|
All the features of one HTTP request can be handled easily with one of the
aforementioned Ajax or HTTP client libraries; the multioperation features will
require some glue code to ensure that all the HTTP requests are completed
successfully (as a "transaction"). At this point, you have not
identified any features of the photo sharing service that are independent of blogs
or file shares, so you will not have to create any new RESTful resources in your Zero application.
Implementing the photo sharing application
Now that you have an understanding of the concepts behind the photo sharing
application, it's time to get your hands dirty. This section assumes that you have
created a new Zero application named photo-share using
Zero's Eclipse plug-in or command-line tool. The instructions that follow reference the command-line interface, but the Eclipse equivalents are easy to find from your project's context menu.
Installing the blog and file share components
The first order of business is to add the blog and file share components to your application. Open the application's Ivy file at /config/ivy.xml and add the following lines:
Listing 1. Adding blog and file share components as dependencies
<dependency org="zero" name="zero.services.blog" rev="1.0.0+"/>
<dependency org="zero" name="zero.services.share" rev="1.0.0+"/>
|
Once you've added the XML in Listing 1, you must run
zero resolve from the command line to finish installing
the two components. Even though their actual code and artifacts have not been added to your project, references to the components from your own code will work at run time thanks to Zero's dependency resolution mechanism.
Using JavaScript to tie the pieces together
The photo sharing actions that map to a single HTTP request (see Table 4) can be implemented very easily using the Dojo JavaScript toolkit. Zero includes the latest version of Dojo (0.4.3), and you can add it to your application by adding the XML from Listing 2 to your Ivy file:
Listing 2. Adding a Dojo dependency
<dependency org="dojo" name="dojo" rev="0.4.3+"/>
|
Dojo makes it easy to transfer HTML form data into HTTP requests, and that is all you will need to do for these single-request actions. The code in Listing 3 shows an HTML form for entering the name of a new photo album and a JavaScript function that creates a new photo album using HTTP POST. Notice how the data taken from the form is added directly to the request body as a JSON object; the implementation of zero.services.blog is expecting a JSON object and will process your request without massaging the data at all.
Listing 3. Creating a new photo album with HTML and JavaScript
<script type="text/javascript" src="/dojo.js">
</script>
<script>
dojo.require("dojo.io");
dojo.require("dojo.json");
dojo.require("dojo.widget.Form");
function createAlbum()
{
var form = dojo.widget.byId("CreateAlbumForm");
var albumData = form.getValues();
var blogName = getPhotoShareName();
dojo.io.bind({
url: '/resources/blogs/' + blogName + '/categories',
method: 'POST',
sync: false,
mimetype: 'text/json',
contentType: 'text/json',
postContent: dojo.json.serialize(albumData),
load: function(type, data) {
alert("The new album was added successfully.");
},
error: function(type, err) {
alert(dojo.errorToString(err));
}
});
}
function getPhotoShareName()
{
return <%= _gc.get("/request/subject/remoteUser") + "-photos"; %>
}
</script>
...
<form dojoType="form" id="CreateAlbumForm" name="CreateAlbumForm">
<table cellpadding="10" cellspacing="0" border="0" width="50%">
<tr>
<td><b>New Photo Album:</b></td>
<td><input type="text" size="64" name="categoryid"/></td>
</tr>
</table>
<br/>
<input type="button" onClick="createAlbum();" value="Submit" />
</form>
|
As you can see, the createAlbum() function takes the
JSON data directly from the HTML form (form.getValues()) and adds it to the HTTP request as a parameter to
dojo.io.bind(). The request is an HTTP POST with the
URI /blogs/blog-name/categories, just as Table 4
described. Notice that even though the HTML tells users they are creating a
new photo album, under the covers you are creating a new blog category to
represent it; similarly, the name of the users' photo share (provided by the getNameOfPhotoShare() method) is used as the name of a blog when
constructing the HTTP request URI. The users do not have to know anything about
your application's use of blogs — all they know is that they now
have a way to organize some of their photos.
Implementing the rest of the single-request actions is as simple as copying the
createAlbum() method and modifying it so that it has a new name and uses a different HTTP method (for example, deleteAlbum() and method: 'DELETE'). In all cases, you will respond to the click of a form button or a page reload to GET, POST, PUT, or DELETE content using the URIs in Table 4.
Handling transactions in JavaScript
The single-request actions aren't hard to implement once you're familiar with the
dojo.io.bind() function, but handling the multirequest
actions requires a bit more thought. Listing 4 shows an HTML form and JavaScript
function for handling the creation of a new photo sharing account. The createPhotoShare() method sends two HTTP POST requests: one to create
a directory on the user's file share and another to create a blog for organizing the photos. Notice that the error handler for the latter undoes the work of the former so that data is not left in an inconsistent state.
Listing 4. Creating a new photo share with HTML and JavaScript
<script type="text/javascript" src="/dojo.js">
</script>
<script>
dojo.require("dojo.io");
dojo.require("dojo.json");
dojo.require("dojo.widget.Form");
function createPhotoShare()
{
var userName = getUserName();
//
// create JSON objects to represent shared directory and blog
//
var directoryData = {
path = userName + '-photos',
contenttype = 'directory',
owner = userName
};
var blogData = {
DESCRIPTION : 'The photo blog for ' + userName,
AUTHOR: userName,
HANDLE: userName + '-photos',
TITLE: 'The photo blog for ' + userName
};
dojo.io.bind({
url: '/resources/file_shares/' + userName,
method: 'POST',
sync: false,
mimetype: 'text/json',
contentType: 'text/json',
postContent: dojo.json.serialize(directoryData),
load: function(type, data) {
alert("The new photo directory was created successfully.");
},
error: function(type, err) {
alert(dojo.errorToString(err));
}
});
dojo.io.bind({
url: '/resources/blogs',
method: 'POST',
sync: false,
mimetype: 'text/json',
contentType: 'text/json',
postContent: dojo.json.serialize(blogData),
load: function(type, data) {
alert("The new photo blog was created successfully.");
},
error: function(type, err) {
alert("Could not complete creation of photo share.");
deletePhotoShare();
}
});
}
function deletePhotoShare()
{
var user = getUserName();
dojo.io.bind({
url: '/resources/file_shares/' + user + '/' + user + '-photos',
method: 'POST',
sync: false,
headers: { 'X-Method-Override' : 'DELETE' },
load: function(type, data) {
alert("The photo directory was deleted successfully.");
},
error: function(type, err) {
alert(dojo.errorToString(err));
}
});
dojo.io.bind({
url: '/resources/blogs/' + user + '-photos',
method: 'POST',
sync: false,
headers: { 'X-Method-Override' : 'DELETE' },
load: function(type, data) {
alert("The photo blog was deleted successfully.");
},
error: function(type, err) {
alert(dojo.errorToString(err));
}
});
}
function getUserName()
{
return <%= _gc.get("/request/subject/remoteUser"); %>
}
</script>
...
<form dojoType="form" id="CreateAlbumForm" name="CreateAlbumForm">
<input type="button" onClick="createPhotoShare();" value="Create My Photo Share!" />
</form>
|
There's a lot to understand in Listing 4, so pay careful
attention. The HTML form is just a button that users can click to create a photo
sharing account in their name. Your default behavior is to name the users' account
(and, by association, their file share and blog) using the pattern
user-photos, where user is the user's login ID. You use this
account name to construct HTTP POST requests in the createPhotoShare() function and HTTP DELETE requests in the deletePhotoShare() function. In both functions, you use dojo.io.bind() to fill in the HTTP request data and handle the responses. The two things that are most interesting about this code are the aforementioned transaction management and the creation of the JSON objects to represent the new shared directory and blog, both of which occur in createPhotoShares(). The latter requires you to know what fields the JSON objects must have in order to be accepted by the components; you can find this information in the components' API documentation on projectzero.org (see Resources).
The other multirequest actions in Table 4 require requests
that look a little different than those in Listing 4, but the structure will remain the same: multiple calls to dojo.io.bind() with error handlers that undo whatever work has already been done. The only exception to this will be the actions associated with photo or account deletion; once you delete part of the account's data, you'll need to complete the task even if some parts of it fail. You can avoid this by performing all the deletion tasks in a server-side script (where you can perform real database transactions), but that optimization is beyond the scope of this article.
Take a moment to notice that, once again, you are using photo-centric names and
values against a back end that is made up solely of blogs and shared directories.
When users visit your photo sharing Web site, they will not see any reference to
blogs or file shares unless they view your HTML source code, making the
application appear very cohesive despite the fragmentation underneath. Finally,
the fact that your two components are useful outside the area of photo sharing
makes your application even more powerful than originally planned. Because the
components are already installed, there's no reason you can't use them to host
"regular" blogs and non-photo file shares for your users, all in one Web site. Neat!
Conclusion
You have explored how Zero components can be tied together using their RESTful
interfaces to provide rich and powerful Internet applications. This approach to
the photo sharing application was built upon the fundamentals of service-oriented
architecture and RESTful resources, leading to an implementation that reused code as much as possible and keeping most of the custom code in your user interface. Like REST itself, your photo sharing application is not really a concrete set of code but more an architectural style. Developers building other types of applications with Zero should look into and consider the components Zero has to offer when planning their implementations.
Resources Learn
Get products and technologies
-
Download Project Zero and start applying the skills learned in this article.
Discuss
About the author  | 
|  | Dan Jemiolo is an Advisory Software Engineer on IBM's Project Zero team in Research Triangle Park, NC.
He is currently working on reusable components for the Zero platform and its service catalog. His previous
work includes the design and development of Apache Muse 2.0 and participation in OASIS Web services standards
bodies. Dan came to IBM three years ago after earning his Master of Science degree in Computer Science from
Rensselaer Polytechnic Institute. |
Rate this page
|  |