April 8, 2008

Experimenting with Google App Engine

Google App Engine was actually the last project I worked on before I left Google. I was the PM of the project when it started, but has grown quite a bit since I left Google last June, and now it is has many more engineers and a handful of extremely talented PMs. I was fortunate enough to be able to see Kevin Gibbs and crew at Campfire One yesterday, and I could barely sit through the whole talk I was so excited to play around with the system.

I have been "meaning to" start a blog for months. Blog software is extremely simple to implement, so I figured it would be a great app to test out on the new App Engine infrastructure. This blog runs on the code I wrote this evening.

The lack of SQL is actually refreshing. Like Django and many other frameworks, you declare your data types in Python:

class Entry(db.Model):
    author = db.UserProperty()
    title = db.StringProperty(required=True)
    slug = db.StringProperty(required=True)
    body = db.TextProperty(required=True)
    published = db.DateTimeProperty(auto_now_add=True)
    updated = db.DateTimeProperty(auto_now=True)

I used a web framework we use at FriendFeed. It looks a lot like the webapp framework that ships with App Engine and web.py (which inspired both of them). It took virtually no effort to get it to work in App Engine thanks to App Engine's support for WSGI.

Running the application looks a lot like the App Engine examples:

application = web.WSGIApplication([
    (r"/", MainPageHandler),
    (r"/index", IndexHandler),
    (r"/feed", FeedHandler),
    (r"/entry/([^/]+)", EntryHandler),
])
wsgiref.handlers.CGIHandler().run(application)

Generating the front page is totally easy:

class MainPageHandler(web.RequestHandler):
    def get(self):
        entries = db.Query(Entry).order('-published').fetch(limit=5)
        self.render("main.html", entries=entries)

Generating the Atom feed is equally easy:

class FeedHandler(web.RequestHandler):
    def get(self):
        entries = db.Query(Entry).order('-published').fetch(limit=10)
        self.set_header("Content-Type", "application/atom+xml")
        self.render("atom.xml", entries=entries)

I wanted to use slugs in my URLs to entries to make them friendlier, so I had to do a query to lookup entries for entry URLs:

class EntryHandler(web.RequestHandler):
    def get(self, slug):
        entry = db.Query(Entry).filter("slug =", slug).get()
        if not entry:
            raise web.HTTPError(404)
        self.render("entry.html", entry=entry)

I also needed security for adding/editing blog entries. App Engine lets you use Google's account system, which is nice for small apps like this. Likewise, it knows which users are "admins" for the app, so I decided to use this built-in role to handle security for the blog: only admins can add/edit entries. First, I wrote a decorator that will automatically add admin security to any RequestHandler method (redirecting to the login page if the user is not logged in):

def administrator(method):
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        user = users.get_current_user()
        if not user:
            if self.request.method == "GET":
                self.redirect(users.create_login_url(self.request.uri))
                return
            raise web.HTTPError(403)
        elif not users.is_current_user_admin():
            raise web.HTTPError(403)
        else:
            return method(self, *args, **kwargs)
    return wrapper

My edit handler looks like this:

class NewEntryHandler(web.RequestHandler):
    @administrator
    def get(self):
        self.render("new.html")

    @administrator
    def post(self):
        entry = Entry(
            author=users.get_current_user(),
            title=self.get_argument("title"),
            slug=self.get_argument("slug"),
            body=self.get_argument("body"),
        )
        entry.put(entry)
        self.redirect("/entries/" + entry.slug)

I don't think this blog will ever get millions of page views, but it is pretty cool that it could in theory :) I didn't have to configure anything. I didn't need to make an account system to make an administrative section of the site. And the entire blog is less than 100 lines of code. I deployed by running a script, and I was done. No machines, no "apt-get install", no "sudo /etc/init.d/whatever restart", nothing.

I am impressed. The App Engine team has done a fantastic job, and I think they have already changed the way I do hobby projects.

The next logical question is: would I run a real business on infrastructure that is so different than everyone else's? If I change my mind about App Engine, what are my options? I am hoping a number of open source projects spring up as alternatives to lower the switching costs over the next year. I will be very interested to see how many startups take the leap and run on App Engine entirely in the meantime.