writing apps the google-y way (brisbane)
DESCRIPTION
Talk from Pamela Fox (me) at YOW 2010 in Brisbane. Covers App Engine and the datastore, with Python examples.TRANSCRIPT
![Page 1: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/1.jpg)
WRITING APPS THE GOOGLE-Y WAYPamela Fox, YOW! Australia 2010 (Brisbane)
![Page 3: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/3.jpg)
Who am I?
Google Maps API Google Wave API
2006 20102008
Google App Engine
![Page 4: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/4.jpg)
Who am I?
wave side projects
92 apps
![Page 5: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/5.jpg)
Who am I?
Java pYthon
![Page 6: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/6.jpg)
What is App Engine?
“Google App Engine enables you to build and host web apps on the same systems that power Google applications.”
http://code.google.com/appengine
![Page 7: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/7.jpg)
What is a “web app”?
![Page 8: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/8.jpg)
Static vs. Dynamic
![Page 9: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/9.jpg)
Anonymous vs. Users
![Page 10: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/10.jpg)
Intranet vs. Internet
~2 billionHundreds - Thousands
![Page 11: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/11.jpg)
What is a “web app”?
![Page 12: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/12.jpg)
Some Google web apps
![Page 13: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/13.jpg)
Some Google App Engine web apps
www.gifttag.comwww.buddypoke.com
![Page 14: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/14.jpg)
Google apps on App Engine
panoramio.com pubsubhubbub.appspot.com
![Page 15: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/15.jpg)
How does App Engine work?
1. You upload application code & resources to Google.
2. Google serves your application from scalable infrastructure.
3. You pay for only the resources that Google used in serving the application.
![Page 16: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/16.jpg)
Demo: Guestbook
awesomest-app.appspot.com
http://code.google.com/p/google-app-engine-samples/source/browse/trunk/guestbook
appengine.google.comlocalhost
build deploy monitor
![Page 17: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/17.jpg)
App Engine architecture
![Page 18: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/18.jpg)
App Engine architecture
user
task
![Page 19: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/19.jpg)
App Engine architecture
![Page 20: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/20.jpg)
App Engine architecture
LIMIT
CPU
LIMIT
Memory
LIMIT
Time
![Page 21: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/21.jpg)
App Engine architecture
hardwareports
globalsfile
system
Groovy, JRuby, Mirah, Clojure, Scala
![Page 22: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/22.jpg)
App Engine architecture
141,241,791 calls1 GB data
$0.15 GB/month
45,000,000 calls
657,000 - 46,000,000 calls
*Always check docs for latest quotas.
192,672,000 calls558 GB data$0.15 GB/month
7,000 - 1,700,000 calls$0.0001 per mail sent
46,000,000 calls 1,046 GB data sent
100,000 - 20,000,000 calls
![Page 23: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/23.jpg)
The tricky bits
![Page 24: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/24.jpg)
Datastore
Entity
PropertiesKey
Entity
Entity
Entity
Entity
Path Kind Name/ID
![Page 25: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/25.jpg)
Example: Speaker Entities
Key Path
Kind ID First Name
Last Name
Speaker1
- Speaker 1 Rod Johnson
Key Path
Kind ID First Name
Last Name
Middle Name Suffix
Speaker2 - Speaker
2 Guy Steele L Jr.
![Page 26: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/26.jpg)
Modeling Speaker Entities
class Speaker(db.model):
firstname = db.StringProperty(required=True)
lastname = db.StringProperty(required=True)
middlename = db.StringProperty()
namesuffix = db.StringProperty()
website = db.StringProperty()
keynote = db.BooleanProperty(default=False)
![Page 27: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/27.jpg)
Saving Speaker Entities
rod = Speaker(firstname="Rod", lastname="Johnson")
guy = Speaker(firstname="Guy", lastname="Steele",
middlename="L", namesuffix="Jr.")
rod.put()
guy.put()
![Page 28: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/28.jpg)
Updating Speaker Entities
rod = Speaker.get_by_id(1)
guy = Speaker.get_by_id(2)
rod.website = "http://www.sexyspring.com"
rod.keynote = True
guy.website = "http://www.lusciouslisp.com"
guy.keynote = True
db.put(rod, guy)
LIMIT!(size/# of batch ops)
![Page 29: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/29.jpg)
Queries & Indexes
Query Index
Index
Index
Index
Query
Query
Query
Query
Query
![Page 30: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/30.jpg)
Queries & Indexes
SELECT * from Speaker ORDER BY lastname
key lastname
Speaker3 Fox
Speaker4 Hohpe
Speaker1 Johnson
Speaker2 Steele
LIMIT!(# of results)
![Page 31: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/31.jpg)
Queries & Indexes
SELECT * from Speaker ORDER by middlename
key middlename
Speaker2 L
![Page 32: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/32.jpg)
Queries & Indexes
SELECT * from Speaker WHERE keynote = True
key keynote
Speaker1 True
Speaker2 True
Speaker3 False
Speaker4 False
![Page 33: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/33.jpg)
Queries & Indexes
SELECT * from Speaker WHERE keynote = False
key keynote
Speaker1 True
Speaker2 True
Speaker3 False
Speaker4 False
![Page 34: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/34.jpg)
Queries
allspeakers = Speaker.all().order('lastname')
for speaker in allspeakers:
print speaker.firstname + '' + speaker.lastname + '' + speaker.website
keynotespeakers = Speaker.all().filter('keynote = ', True)
notspecialspeakers = Speaker.all().filter('keynote = ', False)
LIMIT!(size of results)
![Page 35: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/35.jpg)
Custom Indexes
SELECT * from Speaker ORDER BY lastname, keynote
key lastname keynote
Speaker3 Fox false
Speaker4 Hohpe false
Speaker1 Johnson true
Speaker2 Steele true
speakers = Speaker.all().order('lastname')
.order('keynote')
![Page 36: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/36.jpg)
Custom Indexes
SELECT * from Speaker WHERE lastname > 'Johnson' and keynote = true
key lastname keynote
Speaker3 Fox false
Speaker4 Hohpe false
Speaker1 Johnson true
Speaker2 Steele true
speakers = Speaker.all().order('lastname')
.filter('keynote =', True)
![Page 37: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/37.jpg)
Impossible Indexes
SELECT * from Speaker WHERE lastname < 'Steele' and firstname > 'Gregory'
key lastname firstname
Speaker3 Fox Pamela
Speaker4 Hohpe Gregory
Speaker1 Johnson Rod
Speaker2 Steele Guy
...not in subsequent rows!
![Page 38: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/38.jpg)
Impossible Indexes
SELECT * from Speaker WHERE lastname > 'Fox' ORDER BY firstname
key lastname firstname
Speaker3 Fox Pamela
Speaker4 Hohpe Gregory
Speaker1 Johnson Rod
Speaker2 Steele Guy
...not in the correct order!
![Page 39: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/39.jpg)
Queries with Offset
SELECT * from Speaker LIMIT 2 OFFSET 2
key lastname
Speaker3 Fox
Speaker4 Hohpe
Speaker1 Johnson
Speaker2 Steele
speakers = Speaker.all().fetch(2, 2)
1
2
...slow! LIMIT!(# of offset)
![Page 40: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/40.jpg)
Queries with Cursors
query = db.Query(Speaker)
speakers = q.fetch(1000)
cursor = q.cursor()
memcache.set('speaker_cursor', cursor)
...
last_cursor = memcache.get('speaker_cursor')
q.with_cursor(last_cursor)
speakers = q.fetch(1000)
![Page 41: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/41.jpg)
More Properties
class Talk(db.Model):
title = db.StringProperty(required=True)
abstract = db.TextProperty(required=True)
speaker = db.ReferenceProperty(Speaker)
tags = db.StringListProperty()
pamela = Speaker.all().filter('firstname = ', 'Pamela').get()
talk = Talk('Writing Apps the Googley Way', 'Bla bla bla',
pamela, ['App Engine', 'Python'])
talk.put()
talk = Talk('Wonders of the Onesie', 'Bluh bluh bluh',
pamela, ['Pajamas', 'Onesies'])
talk.put()
![Page 42: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/42.jpg)
Back-References
pamela = Speaker.all().filter('firstname = ', 'Pamela').get()
for talk in pamela.talk_set:
print talk.title
key speaker
Talk6 Speaker2
Talk1 Speaker3
Talk2 Speaker3
Talk5 Speaker4
SELECT * from Talk WHERE speaker = Speaker3
![Page 43: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/43.jpg)
Searching List Properties
talks = Talk.all().filter('tags = ', 'python')
.fetch(10)
SELECT * from Talk WHERE tags = 'Python'
key lastname
Talk1 App Engine
Talk2 Pajamas
Talk1 Python
Talk2 Onesies
LIMIT!(# of index rows)
![Page 44: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/44.jpg)
Update Transactions
commitjournal apply entities
apply indexes
A B
![Page 45: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/45.jpg)
Entity Groups
pamela = Speaker.all().filter('firstname = ', 'Pamela').get()
talk1 = Talk('Writing Apps the Googley Way', 'Bla bla bla',
pamela, ['App Engine', 'Python'],
parent=pamela)
talk2 = Talk('Wonders of the Onesie', 'Bluh bluh bluh',
pamela, ['Pajamas', 'Onesies'],
parent=pamela)
db.put(talk1, talk2)
def update_talks():
talk1.title = 'Writing Apps the Microsoft Way'
talk2.title = 'Wonders of the Windows'
db.put(talk1, talk2)
db.run_in_transaction(update_talks)
![Page 46: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/46.jpg)
Common Features
![Page 47: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/47.jpg)
Counters
1 2 3 4 5people have done something.
![Page 48: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/48.jpg)
RageTube: Global Stats
ragetube.net
http://github.com/pamelafox/ragetube
![Page 49: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/49.jpg)
RageTube: Global Stats
SongStat
yaycountviewcount
title artist
Key
Path KindName(song)
naycount
mehcount
![Page 50: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/50.jpg)
RageTube: Global Stats
viewcount viewcount viewcount
datastore memcache
![Page 51: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/51.jpg)
RageTube: Global Stats
class Song(db.Model): viewcount = db.IntegerProperty(default=0) title = db.StringProperty() artist = db.StringProperty()
def get_viewcount(self): viewcount = self.viewcount cached_viewcount = memcache.get('viewcount-' + self.key().name(), self.key().kind()) if cached_viewcount: viewcount += cached_viewcount return viewcount
@classmethod def flush_viewcount(cls, name): song = cls.get_by_key_name(name) value = memcache.get('viewcount-' + name, cls.kind()) memcache.decr('viewcount-' + name, value, cls.kind()) song.viewcount += value song.put()
@classmethod def incr_viewcount(cls, name, interval=5, value=1): memcache.incr('viewcount-' + name, value, cls.kind()) interval_num = get_interval_number(datetime.now(), interval) task_name = '-'.join([cls.kind(), name.replace(' ', '-'), 'viewcount', str(interval), str(interval_num)]) deferred.defer(cls.flush_viewcount, name, _name=task_name)
LIMIT!(# of tasks)
![Page 52: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/52.jpg)
Ratings
Rated by 500 users.
![Page 53: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/53.jpg)
App Gallery: Ratings
google.com/analytics/apps/
![Page 54: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/54.jpg)
App Gallery: Ratings
Comment Application
total_ratings
sum_ratings
avg_ratingrated_inde
x
comment_count
rating
![Page 55: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/55.jpg)
App Gallery: Ratings
def UpdateAppCommentData(self, rating, operation): def UpdateCommentData(self, rating, operation): self.comment_count += 1 * operation self.sum_ratings += rating * operation self.total_ratings += 1 * operation self.avg_rating = int(round(self.sum_ratings / self.total_ratings)) self.rated_index = '%d:%d:%d' % (self.avg_rating, self.total_ratings, self.index) self.put()
db.run_in_transaction(UpdateCommentData, self, rating, operation)
app.UpdateAppCommentData(rating, db_models.Comment.ADD)comment = db_models.Comment()comment.application = appcomment.rating = ratingcomment.put()
query.order('-avg_rating').order('-rated_index')
![Page 56: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/56.jpg)
Geospatial Queries
![Page 57: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/57.jpg)
City-Go-Round: Agencies
citygoround.org
https://github.com/walkscore/City-Go-Round
![Page 58: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/58.jpg)
City-Go-Round: Geo Queries
AgencyGeoModel
location (GeoPt)
location_geocells (StringListProper
ty)
![Page 59: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/59.jpg)
City-Go-Round: Geo Queries
def fetch_agencies_near(lat, long, bbox_side_in_miles): query = Agency.all() bbox = bbox_centered_at(lat, long, bbox_side_in_miles) return Agency.bounding_box_fetch(query, bbox, max_results = 50)
def bounding_box_fetch(query, bbox, max_results=1000,): results = [] query_geocells = geocell.best_bbox_search_cells(bbox)
for entity in query.filter('location_geocells IN', query_geocells): if len(results) == max_results: break if (entity.location.lat >= bbox.south and entity.location.lat <= bbox.north and entity.location.lon >= bbox.west and entity.location.lon <= bbox.east): results.append(entity) return results
![Page 60: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/60.jpg)
Full Text Search
pizza Search
ThingyIt's like pizza, but in the cloud.
Other ThingyThis will make you smell as delicious as pizza.
![Page 61: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/61.jpg)
Disclosed.ca: Search
https://github.com/nurey/disclosed
disclosed.ca
![Page 62: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/62.jpg)
Disclosed.ca: Search
Contract
agency_name vendor_name
description comments
uri
![Page 63: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/63.jpg)
Disclosed.ca: Search
from search.core import SearchIndexProperty, porter_stemmer
class Contract(db.Model): uri = db.StringProperty(required=True) agency_name = db.StringProperty(required=True) vendor_name = db.StringProperty(required=True) description = db.StringProperty() comments = db.TextProperty() search_index = SearchIndexProperty(('agency_name', 'vendor_name', 'description', 'comments'), indexer=porter_stemmer)
results = Contract.search_index.search(sheep').fetch(20)
![Page 64: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/64.jpg)
Disclosed.ca: Search
Contract
agency_name vendor_name
description comments
uri
search_index(StringListProper
ty)
SearchIndex
![Page 65: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/65.jpg)
Disclosed.ca: Search
key search_index
ContractSearch1 charter
ContractSearch1 june
ContractSearch1 sheep
ContractSearch2 sheep
ContractSearch1 wood
SELECT FROM ContractSearch WHERE search_index = "sheep"
![Page 66: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/66.jpg)
More Learning
http://ae-book.appspot.com
http://code.google.com/appengine
http://blog.notdot.net/
![Page 67: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/67.jpg)
AppEngine: Now & Later
"Run your web apps on Google's infrastructure.Easy to build, easy to maintain, easy to scale."
Roadmap:
App Engine (Standard):• MapReduce• Bulk Import/Export• Channel API• Datastore Options
App Engine for Business:• SQL• SLA + Support
![Page 68: Writing Apps the Google-y Way (Brisbane)](https://reader035.vdocuments.us/reader035/viewer/2022062702/554925c7b4c9059f4c8ba822/html5/thumbnails/68.jpg)
Thanks for coming!