Lightweight


                  on the

by Tim Berglund
Tell me about
aGreat
Application
  Language
Tell me

a
   Happy
    Tale
      of
Deployment
and


Make  it

   Fast
Tell me about

the
      Googl
        eApp
         Engine
An App Server
In the CLoud
Google
Infrastructure
Usually Free!
Quotas




http://code.google.com/appengine/docs/quotas.html
Quotas




http://code.google.com/appengine/docs/quotas.html
Pricing
Languages
Languages
Languages
Languages
Services
Services


It’s an app server
Services


With a few extra
    features
Services
Services
Datastore
Services
Datastore
Authentication
Services
Datastore
Authentication
Caching
Services
Datastore
Authentication
Caching
XMPP
Services
Datastore
Authentication
Caching
XMPP
Email
Services
Datastore         Task Queue
Authentication
Caching
XMPP
Email
Services
Datastore         Task Queue
Authentication    Image API
Caching
XMPP
Email
Services
Datastore         Task Queue
Authentication    Image API
Caching           URL Fetching
XMPP
Email
Services
Datastore         Task Queue
Authentication    Image API
Caching           URL Fetching
XMPP              OAuth
Email
Services
Datastore         Task Queue
Authentication    Image API
Caching           URL Fetching
XMPP              OAuth
Email             Blobstore
But It’s not Perfect
Limitations
Limitations
The JDK whitelist
Limitations
The JDK whitelist
  http://code.google.com/appengine/docs/java/jrewhitelist.html
Limitations
The JDK whitelist
  http://code.google.com/appengine/docs/java/jrewhitelist.html

No Hibernate
Limitations
The JDK whitelist
  http://code.google.com/appengine/docs/java/jrewhitelist.html

No Hibernate
  Only JPA and JDO
Limitations
The JDK whitelist
  http://code.google.com/appengine/docs/java/jrewhitelist.html

No Hibernate
  Only JPA and JDO
Grails is limited
Limitations
The JDK whitelist
  http://code.google.com/appengine/docs/java/jrewhitelist.html

No Hibernate
  Only JPA and JDO
Grails is limited
Performance problems?
Tell me about
Groovy-based
Groovy-based
Lightweight
Groovy-based
Lightweight
Page-centric
Groovy-based
Lightweight
Page-centric
Tightly coupled to the Google App Engine
Groovy-based
Lightweight
Page-centric
Tightly coupled to the Google App Engine
A better GAE than the GAE
Setting up Gaelyk
Serving Content
Serving Content
Serving Content


Static content (like in a WAR)
Serving Content


Static content (like in a WAR)
Groovy templates
Templates
<% include '/WEB-INF/includes/header.gtpl' %>

<h1>Look Up an Artist</h1>

<form action="/artistLookup.groovy" method="POST">
   <input type="text" size="50" name="artist"/>
   <input type="submit" value="Lookup"/>
</form>

<ul>
<% request.artists.each { artist -> %>

   <li>${artist.name}</li>

<% } %>
</ul>

<% include '/WEB-INF/includes/footer.gtpl' %>
Templates
<% include '/WEB-INF/includes/header.gtpl' %>

<h1>Look Up an Artist</h1>

<form action="/artistLookup.groovy" method="POST">
   <input type="text" size="50" name="artist"/>
   <input type="submit" value="Lookup"/>
</form>

<ul>
<% request.artists.each { artist -> %>

   <li>${artist.name}</li>

<% } %>
</ul>

<% include '/WEB-INF/includes/footer.gtpl' %>
Templates
<% include '/WEB-INF/includes/header.gtpl' %>

<h1>Look Up an Artist</h1>

<form action="/artistLookup.groovy" method="POST">
   <input type="text" size="50" name="artist"/>
   <input type="submit" value="Lookup"/>
</form>

<ul>
<% request.artists.each { artist -> %>

   <li>${artist.name}</li>

<% } %>
</ul>

<% include '/WEB-INF/includes/footer.gtpl' %>
Templates
<% include '/WEB-INF/includes/header.gtpl' %>

<h1>Look Up an Artist</h1>

<form action="/artistLookup.groovy" method="POST">
   <input type="text" size="50" name="artist"/>
   <input type="submit" value="Lookup"/>
</form>

<ul>
<% request.artists.each { artist -> %>

   <li>${artist.name}</li>

<% } %>
</ul>

<% include '/WEB-INF/includes/footer.gtpl' %>
Templates
<% include '/WEB-INF/includes/header.gtpl' %>

<h1>Look Up an Artist</h1>

<form action="/artistLookup.groovy" method="POST">
   <input type="text" size="50" name="artist"/>
   <input type="submit" value="Lookup"/>
</form>

<ul>
<% request.artists.each { artist -> %>

   <li>${artist.name}</li>

<% } %>
</ul>

<% include '/WEB-INF/includes/footer.gtpl' %>
Templates
Templates

Lightweight, Groovy JSPs
Templates

Lightweight, Groovy JSPs
Downside: Scriptlets
Templates

Lightweight, Groovy JSPs
Downside: Scriptlets
Request attributes bound from Groovlets
Groovlets
import java.net.URLEncoder

if(params.artist) {
  def response = urlFetch.fetch(new URL("http://musicbrainz.org/
ws/1/artist/?type=xml&name=${URLEncoder.encode(params.artist)}"))
  def text = new String(response.content)
  def xml = new XmlSlurper().parseText(text)
  request.artists = xml.'artist-list'.artist
}
else {
  request.artists = []
}

forward '/artistLookup.gtpl'
Groovlets
Groovlets
Groovy script, meet Servlet
Groovlets
Groovy script, meet Servlet
Intrinsic variables
Groovlets
Groovy script, meet Servlet
Intrinsic variables
Forwarding
Groovlets
Groovy script, meet Servlet
Intrinsic variables
Forwarding
Redirecting
Groovlets
Groovy script, meet Servlet
Intrinsic variables
Forwarding
Redirecting
MarkupBuilder
import java.net.URLEncoder

if(params.artist) {
  def response = urlFetch.fetch(new URL("http://
musicbrainz.org/ws/1/artist/?type=xml&name=$
{URLEncoder.encode(params.artist)}"))
  def text = new String(response.content)
  def xml = new XmlSlurper().parseText(text)
  request.artists = xml.'artist-list'.artist
}
else {
  request.artists = []
}

forward '/artistLookup.gtpl'
import java.net.URLEncoder

if(params.artist) {
  def response = urlFetch.fetch(new URL("http://
musicbrainz.org/ws/1/artist/?type=xml&name=$
{URLEncoder.encode(params.artist)}"))
  def text = new String(response.content)
  def xml = new XmlSlurper().parseText(text)
  request.artists = xml.'artist-list'.artist
}
else {
  request.artists = []
}

forward '/artistLookup.gtpl'
import java.net.URLEncoder

if(params.artist) {
  def response = urlFetch.fetch(new URL("http://
musicbrainz.org/ws/1/artist/?type=xml&name=$
{URLEncoder.encode(params.artist)}"))
  def text = new String(response.content)
  def xml = new XmlSlurper().parseText(text)
  request.artists = xml.'artist-list'.artist
}
else {
  request.artists = []
}

forward '/artistLookup.gtpl'
import java.net.URLEncoder

if(params.artist) {
  def response = urlFetch.fetch(new URL("http://
musicbrainz.org/ws/1/artist/?type=xml&name=$
{URLEncoder.encode(params.artist)}"))
  def text = new String(response.content)
  def xml = new XmlSlurper().parseText(text)
  request.artists = xml.'artist-list'.artist
}
else {
  request.artists = []
}

forward '/artistLookup.gtpl'
import com.google.appengine.api.datastore.*
import static com.google.appengine.api.datastore.FetchOption

if(!user) {
  redirect users.createLoginURL(request.requestURL.toString(
  return
}

def query = new Query("project")
query.addSort("dateCreated", Query.SortDirection.ASCENDING)
PreparedQuery preparedQuery = datastore.prepare(query)

request.projects = preparedQuery.asList( withOffset(0) )

forward "/WEB-INF/views/project/list.gtpl"
import com.google.appengine.api.datastore.*
import static com.google.appengine.api.datastore.FetchOption

if(!user) {
  redirect users.createLoginURL(request.requestURL.toString(
  return
}

def query = new Query("project")
query.addSort("dateCreated", Query.SortDirection.ASCENDING)
PreparedQuery preparedQuery = datastore.prepare(query)

request.projects = preparedQuery.asList( withOffset(0) )

forward "/WEB-INF/views/project/list.gtpl"
import com.google.appengine.api.datastore.*
import static com.google.appengine.api.datastore.FetchOption

if(!user) {
  redirect users.createLoginURL(request.requestURL.toString(
  return
}

def query = new Query("project")
query.addSort("dateCreated", Query.SortDirection.ASCENDING)
PreparedQuery preparedQuery = datastore.prepare(query)

request.projects = preparedQuery.asList( withOffset(0) )

forward "/WEB-INF/views/project/list.gtpl"
import com.google.appengine.api.datastore.*
import static com.google.appengine.api.datastore.FetchOption

if(!user) {
  redirect users.createLoginURL(request.requestURL.toString(
  return
}

def query = new Query("project")
query.addSort("dateCreated", Query.SortDirection.ASCENDING)
PreparedQuery preparedQuery = datastore.prepare(query)

request.projects = preparedQuery.asList( withOffset(0) )

forward "/WEB-INF/views/project/list.gtpl"
import com.google.appengine.api.datastore.*
import static com.google.appengine.api.datastore.FetchOption

if(!user) {
  redirect users.createLoginURL(request.requestURL.toString(
  return
}

def query = new Query("project")
query.addSort("dateCreated", Query.SortDirection.ASCENDING)
PreparedQuery preparedQuery = datastore.prepare(query)

request.projects = preparedQuery.asList( withOffset(0) )

forward "/WEB-INF/views/project/list.gtpl"
import com.google.appengine.api.datastore.*
import static com.google.appengine.api.datastore.FetchOption

if(!user) {
  redirect users.createLoginURL(request.requestURL.toString(
  return
}

def query = new Query("project")
query.addSort("dateCreated", Query.SortDirection.ASCENDING)
PreparedQuery preparedQuery = datastore.prepare(query)

request.projects = preparedQuery.asList( withOffset(0) )

forward "/WEB-INF/views/project/list.gtpl"
URL Mapping
URL Mapping

Templates in war/*.gtpl
URL Mapping

Templates in war/*.gtpl
Groovlets in war/WEB-INF/groovy/*.groovy
URL Mapping

Templates in war/*.gtpl
Groovlets in war/WEB-INF/groovy/*.groovy
Pretty URLs with
war/WEB-INF/routes.groovy
The Datastore
The Datastore
The Datastore
Google’s BigTable infrastructure
The Datastore
Google’s BigTable infrastructure
Schemaless
The Datastore
Google’s BigTable infrastructure
Schemaless
Named entities (property bundles)
The Datastore
Google’s BigTable infrastructure
Schemaless
Named entities (property bundles)
Indexed, queryable, sortable
The Datastore
Google’s BigTable infrastructure
Schemaless
Named entities (property bundles)
Indexed, queryable, sortable
Transactional
The Datastore
Google’s BigTable infrastructure
Schemaless
Named entities (property bundles)
Indexed, queryable, sortable
Transactional
Great Groovy wrapper
Datastore Indexes
Datastore Indexes
Automatically generated by default
Datastore Indexes
Automatically generated by default
Override in WEB-INF/datastore-indexes.xml
Datastore Indexes
      Automatically generated by default
      Override in WEB-INF/datastore-indexes.xml
<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes
  autoGenerate="true">
    <datastore-index kind="Employee" ancestor="false">
        <property name="lastName" direction="asc" />
        <property name="hireDate" direction="desc" />
    </datastore-index>

    <datastore-index kind="Project" ancestor="false">
        <property name="dueDate" direction="asc" />
    </datastore-index>
</datastore-indexes>
Tell me about

  Other
      Services
Email   in Java
Email
                               with Gaelyk


mail.send sender: 'story-admin@augusttechgroup.com',
          to: 'tlberglund@gmail.com',
          subject: 'Story Complete',
          textBody: "A story you edited has increased
in priority. Check it out."
Inbound Email
Inbound Email

Enable in appengine-web.xml
Inbound Email

Enable in appengine-web.xml
Enable built-in servlet in web.xml
Inbound Email

Enable in appengine-web.xml
Enable built-in servlet in web.xml
email.groovy Groovlet
Inbound Email

Enable in appengine-web.xml
Enable built-in servlet in web.xml
email.groovy Groovlet
The “message” intrinsic variable
Memcache
      in Java
Memcache
                             with Gaelyk

def countryFr = new Country(name: 'France')

memcache['FR'] = countryFr

if ('FR' in memcache) {
  countryFromCache = memcache['FR']
}
URL Fetch
        in Java
URL Fetch
                                     with Gaelyk



def response = urlFetch.fetch(new URL("http://example.com"))
def text = new String(response.content)
Task Queues

queue.add countdownMillis: 10000,
          url: '/task/emailTaskList',
          taskName: 'Email current story assignments',
          method: 'PUT',
          params: [date: new Date()]
Task Queues
Task Queues

Timed HTTP requests
Task Queues

Timed HTTP requests
Configure in war/WEB-INF/queue.xml
Task Queues

Timed HTTP requests
Configure in war/WEB-INF/queue.xml
 http://code.google.com/appengine/docs/java/config/queue.html
Task Queues

Timed HTTP requests
Configure in war/WEB-INF/queue.xml
 http://code.google.com/appengine/docs/java/config/queue.html

Can update without redeploying
Task Queues

Timed HTTP requests
Configure in war/WEB-INF/queue.xml
 http://code.google.com/appengine/docs/java/config/queue.html

Can update without redeploying
Can be secured
XMPP
XMPP

User notification
XMPP

User notification
XML payloads for distributed communication
XMPP

User notification
XML payloads for distributed communication
Send messages with a Groovier API
XMPP

User notification
XML payloads for distributed communication
Send messages with a Groovier API
Receive messages through a servlet
XMPP Sending
         in Java
XMPP Sending
        with Gaelyk
XMPP Sending
                                      with Gaelyk

def recipient = 'tlberglund@gmail.com'
if (xmpp.getPresence(recipient).isAvailable()) {
  def status = xmpp.send(to: recipient,
                         body: "Glad you're into Gaelyk!")

  if (!status.isSuccessful()) {
XMPP Sending
                                      with Gaelyk

def recipient = 'tlberglund@gmail.com'
if (xmpp.getPresence(recipient).isAvailable()) {
  def status = xmpp.send(to: recipient,
                         body: "Glad you're into Gaelyk!")

  if (!status.isSuccessful()) {
     // ...
XMPP Sending
                                      with Gaelyk

def recipient = 'tlberglund@gmail.com'
if (xmpp.getPresence(recipient).isAvailable()) {
  def status = xmpp.send(to: recipient,
                         body: "Glad you're into Gaelyk!")

    if (!status.isSuccessful()) {
       // ...
    }
}
XMPP Receiving
XMPP Receiving

Enable in appengine-web.xml
XMPP Receiving

Enable in appengine-web.xml
Enable built-in servlet in web.xml
XMPP Receiving

Enable in appengine-web.xml
Enable built-in servlet in web.xml
/war/WEB-INF/groovy/jabber.groovy
XMPP Receiving

Enable in appengine-web.xml
Enable built-in servlet in web.xml
/war/WEB-INF/groovy/jabber.groovy
The “message” intrinsic variable
Plugins
Plugins
Plugins
Analogous to Grails plugins
Plugins
Analogous to Grails plugins
Mini Gaelyk apps
Plugins
Analogous to Grails plugins
Mini Gaelyk apps
plugins/xxxPluginDescriptor.groovy
Plugins
Analogous to Grails plugins
Mini Gaelyk apps
plugins/xxxPluginDescriptor.groovy
Bundled as ZIP files
Plugins
Analogous to Grails plugins
Mini Gaelyk apps
plugins/xxxPluginDescriptor.groovy
Bundled as ZIP files
Ad-hoc discovery and distribution
Development
What about


      Services
      in development?
GAE Emulation
GAE Emulation
Datastore simulated in filesystem
GAE Emulation
Datastore simulated in filesystem
General Administration
GAE Emulation
Datastore simulated in filesystem
General Administration
  http://localhost:8080/_ah/admin
GAE Emulation
Datastore simulated in filesystem
General Administration
  http://localhost:8080/_ah/admin
Authentication
GAE Emulation
Datastore simulated in filesystem
General Administration
  http://localhost:8080/_ah/admin
Authentication
  http://localhost:8080/_ah/login
GAE Emulation
Datastore simulated in filesystem
General Administration
  http://localhost:8080/_ah/admin
Authentication
  http://localhost:8080/_ah/login
  http://localhost:8080/_ah/logout
Code
On github

git@github.com:tlberglund/august-stories.git

git@github.com:tlberglund/august-demo.git
Thank You
             Tim Berglund
         www.augusttechgroup.com
     tim.berglund@augusttechgroup.com
                 @tlberglund

Using Your Existing Team
Photo Credits
Parchment Background
http://www.photoshopstar.com/effects/recreate-indiana-jones-text-effect/

Gaelyk: Lightweight Groovy on the Google App Engine

Editor's Notes

  • #3 On the JDK, we&amp;#x2019;ve got options, and they&amp;#x2019;re growing. Groovy is a better Java focused on strong Java interop, an easy migration path, and easy application coding.
  • #4 Often the Java deployment story is less than happy. The Ops team gets it and feels great about it, but the PHP guys don&amp;#x2019;t know what to do with us. We&amp;#x2019;ve got great framework choices,
  • #5 It should be an easy framework to learn and use.
  • #6 Demo starting up a Gaelyk app.
  • #8 You&amp;#x2019;re running your app on Google&amp;#x2019;s nearly infinitely scalable infrastructure. Go ahead, DDOS it!
  • #9 You&amp;#x2019;re running your app on Google&amp;#x2019;s nearly infinitely scalable infrastructure. Go ahead, DDOS it!
  • #10 You&amp;#x2019;re running your app on Google&amp;#x2019;s nearly infinitely scalable infrastructure. Go ahead, DDOS it!
  • #11 You can sign up and get started and not pay a dime. You get certain free quotas.
  • #12 GAE offers more than just transfer in and out and CPU. All services have their own quotas described at that URL. Since we&amp;#x2019;re talking about Gaelyk, not GAE, we&amp;#x2019;ll move on.
  • #13 If you grow beyond the free quotas, you can always buy more. Internet scale, baby!
  • #14 Started at Python. Now any language of the JVM, within limitations.
  • #15 Started at Python. Now any language of the JVM, within limitations.
  • #16 Started at Python. Now any language of the JVM, within limitations.
  • #17 Started at Python. Now any language of the JVM, within limitations.
  • #18 Started at Python. Now any language of the JVM, within limitations.
  • #19 Started at Python. Now any language of the JVM, within limitations.
  • #21 Remember, it&amp;#x2019;s basically an app server.
  • #22 Remember, it&amp;#x2019;s basically an app server.
  • #33 Remember, it&amp;#x2019;s basically an app server.
  • #34 Most of java.awt, javax.imageio, some java.nio, etc. Performance problems in Java have to do with first-request performance after a delay of no requests.
  • #35 Most of java.awt, javax.imageio, some java.nio, etc. Performance problems in Java have to do with first-request performance after a delay of no requests.
  • #36 Most of java.awt, javax.imageio, some java.nio, etc. Performance problems in Java have to do with first-request performance after a delay of no requests.
  • #37 Most of java.awt, javax.imageio, some java.nio, etc. Performance problems in Java have to do with first-request performance after a delay of no requests.
  • #38 Most of java.awt, javax.imageio, some java.nio, etc. Performance problems in Java have to do with first-request performance after a delay of no requests.
  • #39 Most of java.awt, javax.imageio, some java.nio, etc. Performance problems in Java have to do with first-request performance after a delay of no requests.
  • #46 All examples will be with TextMate and the Console. No Eclipse plugin necessary!
  • #92 Attachments are optional.
  • #93 Attachments are optional.
  • #120 A discussion
  • #121 PluginDescriptor can add bindings, etc.
  • #122 PluginDescriptor can add bindings, etc.
  • #123 PluginDescriptor can add bindings, etc.
  • #124 PluginDescriptor can add bindings, etc.
  • #125 PluginDescriptor can add bindings, etc.
  • #127 The GAE SDK contains an embedded Jetty instance that emulates all appropriate services. The Eclipse plugin does this as well, but you don&amp;#x2019;t have to use it.
  • #128 It&amp;#x2019;s also easy to deploy to GAE from the command line.