Kay framework dev lounge #1開催

Kay framework dev lounge #1 を開催します。

この集まりは、とりあえずKay自身の開発やKayを使用しての開発に興味があるエンジニアが集まって開発する会にする予定です。他に参加者が居なければ僕一人でKayの開発する予定でしたが、すでに6名参加表明があるので楽しくなりそうです。

申し込みはこちらからお願いします。

不定期に土曜日開催しようと思っていますので、興味があるのみ今回予定が合わない方は、ぜひ次回どうぞ。

実際に使ってくれているユーザーの支援がしたいと思いますし、やっぱり一人だと出る知恵にも限界があるので、開発者が少しずつでも増えてくれると良いな〜と思っています。

App Engine for Java コードラボ


2010/01/16 に App Engine for Java コードラボを開催しました。
応募人数が 30 名で 29 名の参加というこういうイベントでは珍しいほどの高出席率でした!
関心の高さが伺えますね。

このイベントでは、Java のプログラム経験はあるが Google App Engine はまだ触った事が無いくらいの方を対象に、簡単なアプリケーションを作りながらステップバイステップで Google App Engine の使い方を学習する事ができます。

当日の講師陣を紹介(twitterアカウント)しておきます。


そして私です(あまり役には立っていない)。

また当日の模様を少し写真に撮りましたのでよろしければどうぞ。

出席者の方の感想として、殆どの方から高評価を頂きました。準備するこちら側としても大変やりがいのある結果となりました。参加者のみなさん、そして講師陣のみなさん、ありがとうございました。

Android/GAE Hack-a-thon 開催しました

Android と App Engine のエンジニアがコラボしました


明けましておめでとうございます!

2009/12/19 に Android と App Engine のエンジニアを対象にした hack-a-thon を開催しました。この hack-a-thon で参加者は Android と App Engine 両方を使ったアプリケーションを作成します。殆んどのケースでは、Android を UI として使用し、App Engine はデータの永続化に使われる事になります。

また同じ日に、App Engine for Java コードラボという、App Engine の初心者を対象にしたイベントを同時開催しました。hack-a-thon は終日通しての開催だったのですが、コードラボの方は 14:00 くらいに終了なので、コードラボに参加後、hack-a-thon へ戻る事も可能になっていました。

hack-a-thon では基本的にアプリで扱うデータの種類に応じて5つのチームを作成しました。
  1. センサーチーム
  2. テキストチーム
  3. 画像チーム
  4. 地理情報チーム
  5. チュートリアルチーム(コードラボ参加者)
僕はコードラボの方でチューターをしていたので、コードラボ終了後に hack-a-thon の最後に行われるデモだけ見学できました。コードラボ参加者の方々は、アプリのデプロイ方法から始まって App Engine に用意された API の基本的な使用方法や、開発に役立つ細かな知識などを学びました。京都 GTUG のマネージャであり、また App Engine API Expert でもある山下さんが Skype により遠隔から、メインのチューターを担当していただきました(遠隔からメインのチューターというのはやっぱり大変そうでしたね)。彼はまた今回の資料作りにおいて殆んどの部分を担当しております。ありがとうございます!

コードラボはまずまず成功に終わりましたが、今回色々学ぶこともできましたし、参加者のみなさまからのフィードバックも頂けましたので、コードラボの資料や進め方を大きく改善できると思います。1月16日にも第二回目のコードラボを開催予定なのでご期待くださいね。

hack-a-thon に戻ってからは参加者チームのデモを楽しみました。各チームとも一日にしては頑張ったと思います。

センサーチームは ANDROID WARS を作りました


ANDROID WARS は一種のオンラインマルチプレイヤーゲームで、二つの要素から出来ています。一つは Android デバイス上で動作するコントローラーで、もう一つは PC の web browser 上で動作するメイン UI です。メイン UI の方ではきれいな宇宙の画像を背景に、ゲーム参加者のロボットが表示されています。Android デバイス上でフリック入力する事で自分のロボットを動かせ、また Android デバイスを振ると他のロボットを攻撃できます。ゲームはまだ全ての機能が完備していたわけではありませんでしたが、デモはスムースでとても楽しかったです。最終的にこのチームが1等賞を取ったのも頷けます。


テキストチームは VoCtrl を作りました


VoCtrl は声を入力として遠隔の Android デバイスをコントロールするためのアプリケーションです。新しめの Recognizer Intent API を使用していました。残念ながら完成とまではいきませんでしたが、面白いアイデアだと思います。

画像チームは画像交換アプリケーションにトライ


Android デバイスから別のデバイスへ App Engine を経由して画像をやり取りするアプリケーションです。残念ながら完成まではいきませんでした。

チュートリアルチームは HTTP POST を送信するための UI を作成しました(3時間程度で!)


チュートリアルチームのメンバーは全員コードラボへも参加していました。コードラボでは blog ぽいアプリケーションを練習のために作成していたのですが、hack-a-thon に戻った後、今さっき作ったばかりの App Engine Servlet へ HTTP POST する Android アプリを作成しました。3時間しか無かったのにすごいですね!

地理情報チームは位置情報追跡アプリにトライ


オートバイなんかでツーリングする時に使えそうな位置情報追跡アプリを作成していました。ある Android デバイスは自機の位置を App Engine アプリケーションに送信し続けます。App Engine 側ではその位置情報を保存しておき、他の Android デバイスが参照するという作りです。残念ながら Map view を表示しながら GPS を有効にするのがうまくいかなかったので完成には至りませんでしたが、代わりに Google Maps flash API でその位置情報を表示して見せてくれました。


Android API Expert の江川さんによる発表 ...

DBオペレーションに応じてメール送信したい


taskqueue.add に transactional という kwargs を発見したので使い途を考えていたのですが、例えば Entity が put された時にメールを一通送信したいというケースに使えるのではないかと考えています。

まずはこの transactional がどんな効果があるか説明しておきましょう。db の transaction 内で transactional=True を指定して taskqueue.add を実行すると、その transaction が成功した時のみ queue に task が積まれます。

では put 時のメール送信を taskqueue 無しで実装してみましょう。下記のコードは拙作の Kay を使用しています。Kay について知らない場合でも、コード自体単純なので pseudo コードとして読んでもらえれば良いかなと思います。

def add_entity(request):
  # まず Post 値(request.form)を元に Entity を Put
  new_entity = MyModel(name=request.form.get('name'))
  new_entity.put()

  # 次にメール送信
  my_send_mail()

  # クライアントにレスポンスを返す
  return Response("New entity saved.")

このコードだと、db のオペレーションが成功した後にメール送信が失敗した場合に db とメール送信状況に齟齬が生じます。API の呼出し順を変えても問題は解消しません。

そこで transactional な taskqueue を使用する事を考えます。

def send_mail(request):
  # taskqueue で呼ぶハンドラです
  if my_send_mail():
    # 成功した時だけ 200
    return Response("OK")
  else:
    # 失敗したら 500(Retryするはず)
    raise InternalServerError()

def add_entity(request):
  # トランザクションで実行する inner メソッドを定義
  def txn():
    # まずは taskqueue.add を呼ぶ
    taskqueue.add('/send_mail', transactional=True)

    # 次に Post 値(request.form)を元に Entity を Put
    new_entity = MyModel(name=request.form.get('name'))
    new_entity.put()
    return new_entity

  # transaction を実行(本当はエラー処理した方が良い)
  db.run_in_transaction(txn)

  # クライアントにレスポンスを返す
  return Response("New entity saved.")

こうしておくと transaction が成功した場合のみ task が積まれます。また my_send_mail() 呼び出しが失敗したら 500 を返す事で、task をリトライする事ができます。唯一 my_send_mail() が終了してから 200 を返す前に DeadLineExceededError 等になった場合のみ不整合が発生します。Entity個別の情報が必要な場合は、予め id を割り当てる必要があるかもしれません。

論理的にはこれで実用に耐えうるんじゃないかと思いますが、なんせまだ机上で考えただけで実際には試してないので、いずれ試してみたいと思います。

Kay でも blobstore を使う (サムネイルも)


Appengine の SDK-1.3.0 から blobstore が使えるようになりました。blobstore を使用するには専用の webapp handler を使用する事になります。Kay でも blobstore が簡単に使えるように専用の handler を作成しました。Changeset: 212e0d7a2d35 以降で使えます。

殆んど webapp 用のものと使い方は同じですので詳しくは説明しませんが、ここでは blobstore にアップロードされたイメージからサムネイルを作成するようなサンプルを紹介します。今回は blob というアプリケーションを作りました。

blob/urls.py
# -*- coding: utf-8 -*-                                                                                                                                     
# blob.urls                                                                                                                                                 


from werkzeug.routing import (
  Map, Rule, Submount,
  EndpointPrefix, RuleTemplate,
)
import blob.views

def make_rules():
  return [
    EndpointPrefix('blob/', [
      Rule('/', endpoint='index'),
      Rule('/upload', endpoint='upload'),
      Rule('/serve/<resource>', endpoint='serve'),
    ]),
  ]

all_views = {
  'blob/index': blob.views.index,
  'blob/upload': blob.views.UploadHandler(),
  'blob/serve': blob.views.ServeHandler(),
}

blob/views.py
# -*- coding: utf-8 -*-                                                                                                                                     
# blob.views                                                                                                                                                

from google.appengine.ext import blobstore

from kay.utils import (
  render_to_response, url_for
)
from kay.handlers import blobstore_handlers
from werkzeug import Response

from blob.models import ThumbnailImage

# Create your views here.                                                                                                                                   

def index(request):
  upload_url = blobstore.create_upload_url(url_for('blob/upload'))
  blob_key = request.values.get('blob_key')
  d = {'upload_url': upload_url}
  if blob_key:
    d['blob_key'] = blob_key
    ThumbnailImage.create(blob_key)
  return render_to_response("blob/index.html", d)

class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
  def post(self):
    # 'file' is file upload field in the form                                                                                                               
    upload_files = self.get_uploads('file')
    blob_info = upload_files[0]
    headers = {'Location': url_for('blob/index', blob_key=blob_info.key())}
    return Response(None, headers=headers, status=302)

class ServeHandler(blobstore_handlers.BlobstoreDownloadHandler):
  def get(self, resource):
    import urllib
    resource = str ...