Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Web Development

Computing In the Clouds


Google Application Engine

While AWS concentrates on computing resources, the Google Application Engine (GAE) focuses on computing capabilities released via the Google SDK. At this writing, GAE is in preview, but the parts of the overall concept already revealed have stunning appeal. What would your life as a developer be like if you could focus 100 percent of your energy on the business end of your RIA, rather than fretting the details of what happens when you actually attract a significant user base? This is the promise of GAE. By leveraging an agile language (Python) and well-known RIA framework (Django), application development is a matter of fast iterative, incremental coding.

GAE is built on a Web 2.0 infrastructure built on a custom (but open) API. When you download the SDK , you get a local webserver environment suitable for code development and light use testing. The SDK:

  • Includes all of the APIs and libraries available on App Engine.
  • Provides a runtime environment built on Python (2.5)
  • Uses Django for templating and MVC architecture
  • When uploaded to a free account, an app can use up to 500MB of persistent storage and enough CPU and bandwidth for about 5 million page views a month.
  • Web apps run on Google's infrastructure.
  • Apps may use a free domain name on Google's appspot.com domain, or you can also use Google Apps to serve it from your own domain.
  • Apps can be intranetted.

An Example Application

Architecturally, GAE uses a familiar model-view-controller application structure, with the model and controller code conveniently packaged into the same module. To illustrate, we've created a simple To Do List application called "goalpost". You can see how it operates at goalposter.appspot.com. When you log in, you get a screen that resembles Figure 8.

[Click image to view at full size]
Figure 8: GoalPoster hosted at Google's appspot.

Typing in a text string (for example, Finish Canoe and Kayak article), then hitting Enter adds a new goal embodied in the view as a green button. When users complete a goal, they click its button which turns dark brown. Hitting the Clear button clears the completed goals from the view and from the underlying model. Pretty simple compared to all those Outlook or iCal models, but effective and far more likely to be used.

The Code

What's compelling about GAE is the level of abstraction at which you code. An application's code logic can often be a single Python file, with class definitions for the data model, and individual classes implementing the handlers for the various HTTP POSTs and GETs, as in Listing One.

import cgi
import os 
from datetime import * 
from types import *
from google.appengine.ext.webapp import template
import wsgiref.handlers
from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.ext import webapp
## global (semi)constants
gDoneString = "*Done*"
gOpenItem = "openItem"
gDoneItem = "doneItem"
class ItemEntry(db.Model):
  author = db.UserProperty()
  contentID = db.StringProperty(multiline=False)
  content = db.StringProperty(multiline=False)
  date = db.DateTimeProperty(auto_now_add=True)
  status = db.StringProperty() 
  representation = db.StringProperty()

class MainPage(webapp.RequestHandler):
  def get(self):
    results = []
    user = users.GetCurrentUser()
    if user:       # users.get_current_user():
      query = db.GqlQuery("SELECT * FROM ItemEntry WHERE author = :1", user ) 
      results = query.fetch(1000)
    if user:
      template_values = {'todoList': results,  
        'auth_url': users.CreateLogoutURL(self.request.uri),
        'auth_text': 'logout','user_email': user.email(),}
    else:
      template_values = {'auth_url': users.CreateLoginURL(self.request.uri),
        'auth_text': 'login', 'user_email': '',}

    path = os.path.join(os.path.dirname(__file__), 'index.html')
    self.response.out.write(template.render(path, template_values))

class HelpPage(webapp.RequestHandler):
  def get(self):
    template_data = {}
    user = users.get_current_user()
    if user:
      template_data = {'auth_url': users.CreateLogoutURL(self.request.uri),
        'auth_text': 'logout','user_email': user.email(),}
    else:
      template_data = {'auth_url': users.CreateLoginURL(self.request.uri),
        'auth_text': 'login','user_email': '',}
    template_path = os.path.join(os.path.dirname(__file__), 'help.html')
    self.response.headers['Content-Type'] = 'text/html'
    self.response.out.write(template.render(template_path, template_data))  

class ToDo(webapp.RequestHandler):
  def post(self):
    itemEntry = ItemEntry()

    if users.get_current_user():
      itemEntry.author = users.get_current_user()
    itemEntry.content = self.request.get('description')
    d = datetime.now()
    itemEntry.date = d
    itemEntry.contentID = d.__str__().replace(" ", "^")
    itemEntry.status = d.ctime()[4:10]
    itemEntry.representation = gOpenItem
    itemEntry.put()
    self.redirect('/')
     
class MarkDone(webapp.RequestHandler):
  def post(self):
    which = self.request.get('item')
    q = db.GqlQuery("SELECT * FROM ItemEntry WHERE contentID =:1, ORDER DESC by ItemEntry.status" , which) 
    item = q.get() 
    if type(item) is NoneType: #  no specific button pushed
      q = db.GqlQuery("SELECT * FROM ItemEntry WHERE status =:1", gDoneString)
      results = q.fetch(1000)
      for result in results:
        result.delete()
    else:   # the user pressed one button only, so change its state
      if item.representation == gOpenItem:
        item.status = gDoneString # "*Done*" 
        item.representation = gDoneItem
      else:
        d = datetime.now()
        item.date = d
        item.contentID = d.__str__().replace(" ", "^")
        item.status = d.ctime()[4:10]
        item.representation = gOpenItem
      db.put(item) 
    self.redirect('/')  

def main():
  application = webapp.WSGIApplication([('/', MainPage),('/todo', ToDo), ('/help', HelpPage),('/markDone', MarkDone)],debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
  main() 

Listing One

Examining Listing One, notice that there is a class (ItemEntry) whose parent class is db.model and another three descended from webapp.RequestHandler. These latter three correspond to HTTP get and post handlers, which are associated with Web 2.0 URL reference style invocations via the two lines configuring, then running the application:


application = 
  webapp.WSGIApplication([('/', 
  MainPage),('/todo', ToDo), 
  ('/help, HelpPage) ('/markDone', 
  MarkDone)],debug=True)
  wsgiref.handlers.CGIHandler().
  run(application)

The first line connects the three controller functions to the three classes in Listing One. The second starts the application itself. When the user enters a new goal, the browser shifts control to the ToDo controller function. The controller classes implement get or post functions; this too should be familiar to you. ToDo and MarkDone both transfer control using a redirect to '/' which is handled by the MainPage class.

Using Django Templating

In MainPage, the controller code then does a little behind the scenes AJAXian magic to correctly return the page, as seen by the user, replacing the parts of the page's DOM without refreshing the entire page. By convention, the default application view is contained in index.html; see Listing Two.

<html>
  <head>
    <link type="text/css" rel="stylesheet" href="/stylesheets/main.css" />
  </head>
  <body>
     <div id="head">
         <h1><a href="/">GoalPoster</a>: The No Nonsense Personal Goal Manager</h1>
        <div id="topnav">
          {% if user_email %}
          <strong>{{ user_email }}</strong> | 
          {% endif %}
          <a href="/help">help</a> |
          <a href="{{ auth_url }}">{{ auth_text }}</a>
     </div>
    <div id="banner"> 
      <img src="/static/images/Goalpost-Hockey.gif" />   
    </div>
    {% if user_email %}   
    <div id="goals">
    <form action="/markDone" method="post">    
    {% for entry in todoList %}
      <div id="content">
      <p><button id="{{entry.contentID}}" value="{{entry.contentID}}" name="item" 
             class="{{entry.representation}}">{{ entry.status }}  | 
             {{ entry.content|escape }}</button></p>
      </div>
    {% endfor %}
    <input type="submit" value="Clear">
    </form>
    </div>  
    <hr/>
    <form action="/todo" method="post">  
      <div><input type="text" class="content" name="description"></input></div>
    </form>
    {% else %}
    <center><h1><a href="{{ auth_url }}">Login to use GoalPoster</a></h1></center> 
    {% endif %} 
</body>
</html>
An interesting code snippet from index.html is this one:
{% for entry in todoList %}
  <div id="content">
    <button id="{{entry.contentID}}" value="{{entry.contentID}}" name="item" class="{{entry.representation}}">{{ entry.status }}  | {{ entry.content|escape }}</button>
  </div>
{% endfor %}

Listing Two

An interesting code snippet from index.html is:

% for entry in todoList %} <div id="content"> <button id="{ { entry.contentID} } " value="{ { entry.contentID} } " name="item" class="{ { entry.representation} } "> { { entry.status } } | { { entry.content|escape } } </button> </div> { % endfor %}

If you have worked in Rails, TurboGears, Django, or the like, this is probably familiar. Essentially, this segment contains a code directive that the underlying GAE code resolves prior to display. When the GAE code encounters The { %...%} pairs, signal the controller method to interpret the text between delimiters as Python code. Controller class methods return multiple values as a Python dictionary. Wherever it encounters variables appearing between pairs of curly braces, the underlying Django engine substitutes values from the dictionary into the correct template slots in index.html before writing to the browser via self.response.out.write(template.render(template_path, template_data)). The HTML page slots to be filled in are shown bolded in Listing Two. Notice how they match the bolded dictionary references in Listing Two.

Configuration and Testing

Thus far, you've seen the model and controller code expressed in goalpost.py and the view, expressed in index.html. These are specified in the configuration file, (by convention called 'app.yaml') necessary for local testing or for web deployment. YAML was chosen for its succinctness and overall simplicity of form. As in most matters, GAE code and configuration philosophy follows that of Rails: 'convention over configuration', and 'principle of least surprise.' Files exist in standard directory structure and have names as expected by the application runner.

The configuration for goalpost is in Listing Three. Although for this application the configuration specifics are simple, potential configuration details can grow in detail to encompass numerous aspects of a large-scale application.


application: goalposter
version: 1
runtime: python
api_version: 1

handlers:
- url: /stylesheets
  static_dir: stylesheets
- url: /static/images
  static_dir: static/images
  mime_type: image/png,image/gif 
- url: /.*
  script: goalpost.py

Listing Three

Locally, an application may be exercised from the command line using the dev_appserver supplied with the SDK. The general invocation signature of dev_app server is:


dev_appserver.py [options] <application root>

where a number of options are possible. Thus, a typical terminal window invocation for the sample application would be:


python2.5  /usr/local/bin/dev_appserver.py   -- debug  -- port=8080   GoalPost/

You can also use the GoogleApp Engine Launcher application manager on Mac OS/X to give you a boost up in creating and managing a new application or datastore if you're command-line averse; however, most developers find their favorite editor and the command-line tools sufficient. The rather remarkable aspect of writing a GAE-based app is how succinct both code and the configuration can be.

Registration and Upload

Once you've satisfactorily tested your application locally, you can register and upload all required files. Registration is a one-step process in which you supply a mobile phone number to which a registration number may be sent. After you receive a registration code, you can manage deployment and observe the application's performance and resource consumption using the handy administration console at appengine.google.com/dashboard.

If you want to have Google host your application you need to supply an application name, which is prepended to the domain appspot.com. For the sample application the name goalposter was used; thus the application resides at goalposter.appspot.com, as in Figure 8.

Clearly, GAE lets you write applications at a very high level of abstraction, where underlying machine architectures, memory and disk limits tend to disappear; further, there are no 'seams' visible to either application developers or end users. A typical application can rely on there being enough resources to support its needs almost regardless of the number of concurrent users.

BigTable

One reason for the scalability of GAE is the ability to support distributed data storage for an extremely large corpus delivered through Google's 'BigTable' architecture. Google asserts BigTable has the ability to deliver high-performance interaction data in the range of several hundred terabytes. BigTable is essentially a large flat space that provides object-relational semantics does not really implement a full relational model. Although BigTable applications such as goalposter use what appears to be a classic object-relational model, the underlying mechanisms for access, data versioning, and garbage collection share little with 'real' RDBs such as MySQL.

Even so, both object.method (for example, itemEntry.put() in Listing One) semantics and a more general call semantic resembling classic embedded SQL (the GqlQuery queries in Listing One) are implemented. If you understand either or both models from traditional programming or full scope styles such as Rails or TurboGears, you already understand enough to get started writing your first GAE cloud-hosted application.

Summary

One thing of which we can be certain as developers is that cloud computing is going to expand in overall level of acceptance in the enterprise. The concept will continue to mature and gain both features and developers. As this article goes to press, Amazon has just announced CloudFront, a content delivery/content management service based on other Amazon services such as S3. Google continues to mature and update its GAE capabilities. Even Microsoft has decided to get its cloud on with a service platform called 'Azure. ' The weather out there for us developers is becoming at the same time both more cloudy and brighter.

Acknowledgement

Thanks to Prakash Manghawani for his help on this article.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.