GAEの勉強メモ

勉強中。気になったことをメモしていく。

オープンソース徹底活用 Slim3 on Google App Engine for Java

参考URL(徐々に増やしていく)

Google App Engine の制限

  • アクセスの度にインスタンスが生成される(2〜3分アクセスがないと終了するらしい。思ったより早い。)
  • 月500万PV相当まで無料(アプリの作りによって実際にどの程度無料で使えるのかは結構変わるらしい)
  • Thread Socketがない、ファイルシステムに書き込めない(作る上でどの程度影響があるのか?)
  • 30秒以内にレスポンスを返さないといけない(結構余裕?)
  • spin-up時間が重要(2秒以内くらい?)
  • Java はリクエスト処理に優れている。テスト、リファクタリングしやすい
  • Python は spin-up 速い。

Slim3 の設計哲学

  • "Less is more" (GAE用にカスタマイズされ、よけいな機能は入れてないとのこと。)
  • spin-up 至上主義 (やっぱり spin-up 時間は重要らしい。ユーザへの反応にダイレクトに繋がってしまうため)
  • パフォーマンス重要(課金が発生する)

Bigtable

  • 多次元ソート済みマップ
  • KVS(Key-Value Store) (RDBMS ではない。これは重要)
  • 行と列と時間軸の多次元(カラムの構造は結構柔軟。どうやって使いこなすのか)
  • 一行に対する処理はアトミック(行単位の追加更新削除が基本か。)
  • 自動的に分割/統合を行い負荷分散する仕組みがある(これが大量のデータを管理できる肝)
  • インデックス、クエリ、トランザクションの細かい説明は眠くなるのでざっと読んで飛ばし。使うときにちゃんと見る。(P34 - P70) まずは動かす! 実行環境を整える
インストールマシン:MacBookPro Snow Leopard 
入れたもの↓
Eclipse IDE for Java EE Developers Mac OS X 64 Bit
Google Plugin for Eclipse  Eclipse 3.6 (Helios): http://dl.google.com/eclipse/plugin/3.6
slim3-blank-1.0.5.zip
  • 環境はできた。eclipse 上で本に書かれたテストをやってみる。(P85)
  • いきなりテストコードが書かれて実行結果が載っているだけでどうやればいいのかわからない
  • test 配下にパッケージ chapter5 を作成する
  • class を chapter5 配下に作成する。AppEngineTestCase を継承
  • テストを1件以上書いて(まずはお約束の Hello slim3)「Run As」選択して「JUnit Test」を選択する
  • 上手く行けばコンソールに結果が出る。
  • ふぅ、ハマった。こういう本は途中の eclipse の操作までは書いていないから知識ゼロでは苦労する。
  • 久しぶりの Java on Eclipse
  • eclipse 快適。コード補間は気持ちいいね。サクサク書ける。
  • テストケースのメソッドを日本語で書けるってどういうこと? アノテーションってすごい。@Test
  • やっぱりコンパイルする前からエラーがわかるってすごいな。こういう環境で開発したい。
  • これから使う I/F の使い方を確認するためにテストコードを書くというのは非常に有益だと思う。

いったんGoogleAppEngine上にあげてみる

  • slim3 での Model の使い方、データベースへのアクセスの仕方が書いてあるが実際に画面の出るところを試したくなった
  • 本から逸れて右のサイトを参考に IndexController を作成。 http://www.yuyak.com/blog/244/ 
  • slim3-blank から [Run As]-[WebApplication] でローカルサーバが立ち上がる

App Engine 上に上げてみる

  • ツールバー上の GAEアイコンをクリック
  • アプリケーションID を取得
  • Eclipse 上で必要な情報を入力してデプロイ完了! 意外と簡単。
  • 【疑問】IndexController が複数できているがどちらが動いているのか不明。→一つは IndexControllerTester だった!
  • サンプルを動かしてみる
  • 実際にデータベース登録とかしてるサンプルを探す。http://d.hatena.ne.jp/hoshisoft/20091202/1259752391
  • 誤記がいくつかあったが修正してローカルでは動いた
  • そのままデプロイしてみたらキチンと動いた! データベース登録もできてる。これはすごいぞ。
  • データベースの内容は管理ページの Data - Datastore Viewer で確認できる。

Todoリストアプリを移植してみる

  • Todoリストアプリをどこまで移植できるか試してみる。
  • Topページのベタ書きとCSSは移植できた。 cgi でやっているようなテンプレートに初期データの設定をやるような仕組みはできるのか?
  • Slim3のことをTwitterでつぶやいていたらある人がフォローしてくれた。その人のページ。参考になりそう。
  • GWTというGoogleAppEngine用のツールキットがあるらしい。便利そうだがまずは自力で書いてみる。
  • Ajax でデータを送信してデータベースに登録してみる
  • jsp側で、jQuery の $.ajax() でデータを post する。
  • url の第一引数に指定した名前で controller を追加する (todo/addtask と入力した)
  • AddtaskController::run() 内では、request という変数に Ajaxのパラメータが入っている
  • map に変換して TodoTaskService に渡す
  • TodoTaskServiceで TodoTask モデルを生成してデータベースに登録できた
  • 登録と同時に更新日時を付加するにはどうしたらよいのだろう? (2010/10/16)
  • DataStoreから保存済みのModelを読み込む。
        // DataStoreから保存済みのデータを読み込んで表示してみる
        TodoTaskMeta todotaskmeta = TodoTaskMeta.get();
        List<TodoTask> tasklist = Datastore.query(todotaskmeta).filter(todotaskmeta.name.equal("USER_NAME")).asList();
  • ModelのmetaデータをとってDatastoreのqueryに渡す。
  • filterでデータ抽出の条件を設定。この例では、name属性が "USER_NAME" のものを取得する。
  • Listとして取得する。
  • あとは List内を漁ってそれぞれの属性にアクセスする。

Controller で取得したデータを jps上に表示するには?

  • サーブレット上のプログラムと jsp上の表示をリンクする方法がよくわからない。どこから調べたらよいのか‥。JSPの基本構文の知識と、変数のスコープ辺りの知識(requestとか)が必要そう。いろいろ調べてみる。
  • サンプルでは Controller::requestScope() を使って jsp から request スコープで参照できるようにしているっぽい。
  • jsp側ではスコープ変数のメソッドや JSP Functions などで変数にアクセスできるらしい。
    • 例えば、 IndexController 側で以下のように書いて
        requestScope("name", "baggio");
  • index.jps 側では以下のように使うとテキストボックスに baggio が入る。2行目は普通に表示。
    <td><input type="text" ${f:text("name")} class="${f:errorClass('name', 'error')}"/>${f:h(errors.name)}</td>

    <%=request.getAttribute("name")%>
  • Beans の配列をサーブレット(Controller)から JSP に送る方法がわかった。(興奮!)
    • Controller 側
        List<TodoTask> tasklist = Datastore.query(todotaskmeta).asList(); // DBからListでタスク取得        
        requestScope("taskList", tasklist); // Listを request スコープとする
<jsp:useBean class= "java.util.ArrayList" id="taskList" scope="request" />

<c:forEach var="element" items="${taskList}">
<li><c:out value="beans : ${element.name} ${element.msg}"/><BR>
</c:forEach>
  • 送りたい Beans を ArrayList に突っ込んで requestScope()する
  • JSP側では useBean で ArrayList する
  • forEach タグと out タグでBeansの中身を参照できる。素敵。
  • ある程度まとまったタグは javascript 側に書くことで関数化できる。
<c:forEach var="element" items="${taskList}">
<script language="JavaScript">
<!--
document.write(createTaskElement(${element.key.id}, "${element.msg}"));
// -->
</script>
</c:forEach>
        response.getWriter().write(todotask.getKey().getId() + "!" + todotask.getMsg() + "!9,9,9,9,9,9");
  • 上のように、response の getWriter().write() に文字列を渡すと Ajaxのリスナーに渡せた

タスクの移動を実装してみる

  • 移動の際に Ajax を投げる部分を cgi からコントローラ名に変更 todo.js : todo.cgi → updatetask
  • コントローラを追加。Viewなしで "todo/updatetask" という名前を設定
  • UpdatetaskController の処理はリクエストのMapを作ってサービスクラスに渡すだけ
  • サービスクラス(TodoTaskService)に updatetaskメソッドを追加
  • リクエストマップから id を取得して Key を生成
  • Key を使って Datastore から TodoTaskモデルを取得
  • BeanUtil.copy() でモデルを更新
  • Datastore.put() でデータベースを更新
  • 追加したタスクの日本語が化ける
  • jsp から送信した文字をコントローラから返すと ??? が表示されてしまう
  • コントローラでリクエストを受け取る前にエンコードモードを指定して上げる必要がある
  • また、コントローラからデータを送る前にもエンコードモードを指定してあげる
        request.setCharacterEncoding("UTF-8"); //★ここ重要!
        RequestMap requestMap = new RequestMap(request);
        requestMap.put("status", "0");
        TodoTask todotask = service.addtask(requestMap);
        response.setContentType("text/html;charset=UTF-8"); //★ここ重要!
        response.getWriter().write(todotask.getKey().getId() + "!" + todotask.getMsg() + "!9,9,9,9,9,9");
  • それぞれ、request からデータを取り出す前、レスポンスから getWriter() を行う前にやる必要がある。

ログイン認証の仕組みを入れてみる。

(5) session の設定
 googleappengine/war/WEB-INF/appengine-web.xml に1行追加します。

<?xml version="1.0" encoding="utf-8"?>
          : 中略
<sessions-enabled>true</sessions-enabled> <--追加-- > 
</appengine-web-app>
  • filter でユーザ名を追加したら、サーバ上でエラーになった。Datastore Indexes が Building のままになっている。
  • しらべたら、サーバ上でインデクスを作成しているとのことでしばらく時間がかかるとのこと。待ってみる。