2012年3月29日木曜日

Google App Engine Go SDK 1.6.4を使おうと思ったものの…。

今日の早朝(1:00頃)Go Ver 1のリリースと、
Google App Engine Go SDK 1.6.4がリリースされたので、
変更点をまとめようかと思ったのですが、go1beta4を利用していると
あまり大きな変更点はないので、変更点を洗い出すよりは、開発の手順を
まとめてみようと思います。

とりあえず、ネタを考えるのが面倒だったので
Slim3のチュートリアルと同じ事をGAE/G 1.6.4で実現できるかどうかを
試しつつ進めていこうと思います。

  1. コントローラとテストの作成
事前に、プロジェクトのディレクトリを作成して下さい。
$ cd [任意のディレクトリ]
$ mkdir twitter
$ cd twitter

Slim3のように、build.xmlなんて、便利なものは存在しませんので、手で作成します。

app.yaml
application: twitter
version: 1
runtime: go
api_version: go1

handlers:
- url: /favicon.ico
  static_files: favicon.ico
  upload: favicon.ico

- url: /.*
  script: _go_app

app.yamlの大きな変更点は、api_versionが"go1"になっている点です。
1.6.3までは、"3"で、go1betaは"go1beta"でした。
恐らく、古いバージョンはコンパイルエラーが発生して動かないと思いますので
古いバージョンのプログラムから持ってくる場合はチェックしましょう。

次に、controllerを作ります。
$ mkdir controller
$ cd controller

frontcontroller.goを作成します。このファイルが名前の通り、frontcontrollerとなります。

package controller

import (
 "fmt"
 "net/http"
)

func init() {
 http.HandleFunc("/twitter/", index)
}

func index(w http.ResponseWriter, r *http.Request) {
 htmlText := `
     <!DOCTYPE html>
     <html>
     <head>
     <title>Hello World</title>
     </head>
     <body>
         <p>Hello World</p>
     </body>
     </html>
     `
  
 fmt.Fprintf(w, "%s", htmlText)
}

では、実行します。
開発サーバはdev_appserver.pyを実行するだけなので、twitterディレクトリの1つ上の
ディレクトリで実行すると便利です。

$ python [SDKのディレクトリ]/dev_appserver.py --high_replication twitter

ブラウザで、http://localhost:8080/twitter/にアクセスして、Hello Worldが
表示されれば成功です。
--high_replicationのオプションはHRD環境をエミュレートするオプションです。
現時点でこのオプションを付ける必要は無いのですが、とりあえず入れておきます。

この後、デモでは、フォームの作成で、モデルの処理を作成して、テストコードを
作成してテストを動かしてテストして…と続くのですが、

Go言語標準のコマンド、go testが動作しないため、断念しました。
https://plus.google.com/u/0/114183076079015753160/posts/intFioopvTj

ドキュメントにも載っていないし、恐らく、現時点ではGAE/GのSDKでgo testを利用する事はできないのかもしれません。

ただ、GAE/Gにもtest用のモジュールが存在しているようで、
https://code.google.com/p/gae-go-testing/source/browse/appenginetesting/
これを利用したら良いのではないかと考えている最中です。

17:16:追記
gae-go-testingはだいぶ古そうです。試しにmake installしてみましたが、だめでした。
unit testについてはもう少し様子を見たほうが良さそうです。

もしくは、標準のGo言語のコンパイラをインストールしてGAEのAPIを動かさない
パッケージのみテストを行うという方法を取るしか今は無さそうです。

2012年3月27日火曜日

Google API Expertが解説するClosure-libraryプログラミングガイドを読む(2)



今日も上記のClosure-library本を読み進めているのですが、
少し、DOM操作のコードを動かしてみたので、
DOM操作についてのメモを残しておきたいと思います。

まず、base.jsとdeps.jsを取り込むコードを入れておきます。
  <link rel="stylesheet" href="closure-library/closure/goog/css/common.css" />
  <link rel="stylesheet" href="closure-library/closure/goog/css/hsvpalette.css" />
  <script src="closure-library/closure/goog/base.js"></script>
  <script src="deps.js"></script>

で、最初にDOMのモジュールを読み込みます。
<script>
    // DOMの追加モジュールの読み込み
    goog.require('goog.dom');
    goog.require('goog.dom.DomHelper');
</script>

次に実際にDOMの追加です。
<script>
    // iframeをbodyに追加する。
    var iframe = goog.dom.createDom('IFRAME', {'src': 'about:blank'});
    goog.dom.appendChild(document.body, iframe);

    var fdoc = goog.dom.getFrameContentDocument(iframe);
    var helper = goog.dom.getDomHelper(ifdoc);

    // iframeの中にdivとspanを追加
    var node = helper.createDom(
          'DIV', null,
          helper.createDom('SPAN', { 'style': 'color: red;' }, 'Hello '),
          helper.createDom('SPAN', { 'style': 'color: blue;'}, 'World!'));
    helper.appendChild(ifdoc.body, node);
</script>

ソースコードを見ていただくと分かりますが、 bodyにiframeを追加して、iframeの中にHello World!が追加されます。 createDom()が要素の生成で、appendChild()が要素の追加。 getDomHelper()でhelperを取得して、helperを通じて、 要素の子要素を追加するという感じになっています。

CSSセレクタを使った要素の取得もできるようなのですが、
どうやら、closure-libraryと違うライセンスが適用されるようで、
商用利用の場合は、気をつけたほうが良いようです。

使い方は、こんな感じだそうです。

var hoge = goog.dom.query('#list > li:nth-child(hoge)');


オープンソースカンファレンス2012@愛媛に参加しました。

オープンソースカンファレンス2012 Ehimeに参加してきました。

今回は、中国GTUGの開催ではなく、四国GTUGが出展するので、
セッションを担当してきました。

当日の資料

今回は四国GTUGのブースに行ってもらう事を促す内容を
多くしてみました。実際、効果があったのかどうかはわかりませんが、
ある程度、効果があったと信じています。

次に、Go言語でプログラミングをする最初の一歩として、
開発環境、情報源などを紹介。

最後に、Go言語のライブコーディングをしながら、
書き方の説明を2例(本当は4例ぐらい考えていましたが、
前半で時間を使い過ぎて足りませんでした)

おまけにプレゼント(謹呈本)を「会場から一番近い人」に渡しました。

一番近い人にした理由は、大学生か、地元の人の可能性が高いと
踏んだからです。(後でまとめますが)今回のセッションの参加者は
県外の方が多かったように見受けられたので、せっかく愛媛で開催
しているのに県外の方に渡すのはおもしろくなかったので。

セッションの参加者は25人程度だったように思います。
事前申し込みは20人。同一時間帯のセッションの中では1位でした。
(運営側がほとんど、裏番組の宣伝を行ってくれるので、1週間前は4人とか
でしたが、何とか盛り返したということでしょうか。)

そして、Twitterなどでも、私のセッションが「おもしろかった」と
言っていただいた方がいらっしゃるようで、任務は全うできたかなと思っています。
Go言語の利用者の人口が増えるかどうかは今後の様子を見守ろうと思います。

岡山から愛媛まで、3時間半程度かかったようなので、やっぱり当日移動だと、
他の人のセッションが聞けないので残念でした。
(懇親会の為に途中、一旦ホテルにチェックインしに行っていて、
自分のセッション+ブースでの待機ぐらいしかできなかった)

懇親会にも参加させていただいたのですが、
久しぶりにすみともさん、ありやまさんとお話して、「関西には来ないのか?」
と言われたので、ある程度、余裕ができたら、
何らかのイベントに顔を出しに行きたいと思います。

最後に、のがたさんとお会いしたので、昨年から開発していた、「Sahanadroid」の
開発終了の相談をさせていただきました。
急なオファーから始まった開発でしたが、途中でサーバとの連携テストを残して、
中断状態が続いていたのですが、決着をつけないといけなかったので
私から切り出すことにしました。
ソースコードはすべて公開してありますので、参考になるかどうかわかりませんが、
興味がありましたら見ていただけたらと思います。

Sahanadroidのソースコード(Android 2.2で開発)
https://github.com/tyokoyama/SahanaClient
テストプロジェクト
https://github.com/tyokoyama/SahanaClientTest

このプロジェクトで、貴重な経験ができましたので、今後も何らかの形で
協力できればと思っています。

2012年3月21日水曜日

Google API Expertが解説するClosure-libraryプログラミングガイドを読む(1)


土曜日(3/18)に上記の本を購入したので、読んでみることにしました。
Closure-libraryというのはGoogleが開発したJavaScriptライブラリで
GmailやGoogle Docsなどで利用されているそうです。

このライブラリは「手軽な開発」を助けるというのが目的ではなく、
開発を「より大きくする」事を実現するためのライブラリのようです。

したがって、小規模なアプリケーションを作成するのは不得意だと
書籍に書かれていました。

今回は2章の最初のサンプルを作る所まで読み進めました。
最初のサンプルは、HsvPaletteクラスを利用した色選択パレットを表示する
ものでした。

先にポイントを整理しておきます。
ポイント:

  • 書籍はローカルのファイルアクセスで結果を確認しているが、現時点でローカルファイルアクセスだと正常に動作しない可能性がある。(HsvPaletteはグラデーションが表示されなかった。どうやら、url指定があり、外部サイトにアクセスするみたい。)
  • Webサーバを用意する必要がある。(私は、GAE/Gの開発サーバを利用)

動作確認環境の構築方法です。(※GAE/Gを利用する場合
GAE/G SDKのバージョンはgo1beta4を利用しました。(static_fileの設定を付けるので、ローカルの確認だけなら、バージョンは何でも良いと思います)


  • closure-libraryをダウンロードする。

ダウンロードは書籍の通りで大丈夫です。


  • 書籍の通りにサンプルのソースコードを実装。



  • app.yamlを作成する。

application: sample
version: 1
runtime: go
api_version: go1beta

handlers:
- url: /style\.css
  static_files: html/style.css
  upload: html/style.css

- url: /closurelib
  static_dir: closurelib

- url: /closure-library/closure/goog/css
  static_dir: closurelib/closure-library/closure/goog/css

- url: /closure-library/closure/goog/base\.js
  static_files: closurelib/closure-library/closure/goog/base.js
  upload: closurelib/closure-library/closure/goog/base.js

- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon.ico

- url: /.*
  script: _go_app


  • 開発サーバの起動

python [SDKのパス]/dev_appserver.py [プロジェクトディレクトリ]


  • http://localhost:8080/closurelib/index.htmlを表示する。


Google+のメモ
https://plus.google.com/u/0/114183076079015753160/posts/hUJwV935qnd

2012年3月14日水曜日

Slim3のテストを行う時に気をつけること(Datastore編)

開発しているGAEのアプリケーションのデータのメンテナンスを行う必要があるので、
メンテナンス用のコントローラを作成してテストを行おうと思って、テストコードを
書いていました。

テストコードをそのまま掲載するわけにはいかないので、適当なコードを載せます。

public class HogeControllerTest extends ControllerTestCase {
     private HogeService service = new HogeService();

    @Override
    public void setUp() throws Exception {
        super.setUp();

        // データの登録
        Hoge hoge = new Hoge();
        hoge.setId(1);
        hoge.setName("abcd");

        service.put(hoge);
    }

    @Test
    public void run() throws Exception {
        ApiProxy.setEnvironmentForCurrentThread(new UnitTestEnvironment());
        
        tester.start("/hoge");
        HogeController controller = tester.getController();
        assertThat(controller, is(notNullValue()));
        assertThat(tester.isRedirect(), is(false));
        assertThat(tester.getDestinationPath(), is(nullValue()));
        
        List<Hoge> list = service.query().asList();
        assertEquals(1, list.size());                     // ここでエラー
    }
}

UnitTestEnvironmentクラスのソースです。
public class UnitTestEnvironment implements Environment {

    @Override
    public String getAppId() {
        return "Unit Tests";                // setupを使う場合はこれにしておかないとquery()でデータが取れない。
    }

    @Override
    public Map<string, object> getAttributes() {
        Map<string, object> map = new HashMap<string, object>();
        map.put("com.google.appengine.server_url_key", "dummy");
        return map;
    }

    @Override
    public String getAuthDomain() {
        return "gmail.com";
    }

    @Override
    public String getEmail() {
        return "admin@gmail.com";
    }

    @Override
    public String getRequestNamespace() {
        return "";
    }

    @Override
    public String getVersionId() {
        return "unittest";
    }

    @Override
    public boolean isAdmin() {
        return true;
    }

    @Override
    public boolean isLoggedIn() {
        return true;
    }

}

上記のUnitTestEnvironmentだとテストは正常終了します。
これは、setupでのデータストアの書き込み時のapplicationIdと
テスト実行時の呼び出しの時のapplicationIdが一致するからです。

失敗していた時というのが、TaskQueueのテストをしようとして参考にした
UnitTestEnvironmentのapplicationIdが"MyApplicationId"とかにしていたので
setUpの時とapplicationIDが異なってしまっていました。
その影響で、何度queryを実行してもデータが1件も取れないという状況に
陥っていました。

なので、setupを使う時というか、データストアを使ったテストを行う場合は
以下の点に注意した方が良さそうです。

  1. appidをデータ登録時と参照時で合わせておく。
  2. Slim3のTestEnvironment.javaで設定されているappIdは"Unit Tests"

2.はsetupを使わず、自分でデータを登録する場合は問題ないと思いますが、
初期データなど使う場合や、事前にlocal_db.binを用意する場合などは
気をつけておかないとデータが取れない現象に遭遇するかもしれません。

以下、参考までに当時の私のG+に投稿したコメントです。

TaskQueueに入っている事を確認するテストで苦戦している様子(Google+)
https://plus.google.com/u/0/114183076079015753160/posts/NUceVDbNKDK

データストアからフィルタなしのqueryでデータが取れなくて苦戦している様子(Google+)
https://plus.google.com/u/0/114183076079015753160/posts/MZhuGtjrwTS

Slim3 Source Code Reading #12

今日は、Slim3 Source Code Reading #12でした。

今回の内容はRouterを読み進めました。ソースファイルは以下の4ファイルです。

  • Router.java
  • RouterFactory.java
  • RouterImpl.java
  • Routing.java

少し前に、私が仕事で開発しているGAE/JのアプリケーションでRouterを導入したので
ソースコードを事前に読んでいました。
(Googleで検索せずにFrontController.javaを読み進めて実装方法を解析したのは、ソースコードリーディングをした影響でしょうか。)
また、@sinmetalさんと2人だったので、ざっと読んだ感じになりました。

最初にRouter.javaですが、Routerクラスはインターフェイスで、
静的ファイルの判定と、リクエストURLを変換した後のURL文字列を返すメソッドの2つがあります。

次に、RouterFactory.javaですが、これはFrontControllerからRouterを呼び出すために
インスタンスを生成するクラスです。
getRouter()は定義したRouterか、デフォルトのRouterを取得します。
ここで取得したRouterのインスタンスはservletContextに格納され、2回目以降は、キャッシュされたインスタンスが取得されます。
初回は、Routerのインスタンスを生成しますが、そのメソッドがcreateRouterメソッドです。
この中身を見れば分かるのですが、自前のRouterはRootPackage.controllerPackage.AppRouterクラス固定となっています。
配置する場所も、名前も間違っていると反映されないことになります。

RouterImplクラスは、Slim3を使う上ではデフォルトのRoutingの処理が書かれています。
仕組みは、ListにRoutingのルールを追加しておいて、Listの中に対象のURLがあるかどうかを判定するようになっています。URLがあれば、変換後のURL文字列を返します。
静的ファイルの判定(isStaticメソッド)は以下の様になっています。

  • /_ah/で始まらない。(管理画面などのURL)
  • 最後が拡張子ではない(.jpgなどで終わらない)か、拡張子が"s3"で始まらない

拡張子が"s3"で始まるファイルは静的ファイルではないと判定するようです。
そんなファイルがあるかな?と考えてみましたが、その時は思いつきませんでした。
クラスにはS3で始まるクラスもあるようですが、”拡張子”がs3で始まるファイル…。

最後にRoutingクラスですが、このクラスは、変換前のURL文字列と変換後のURL文字列を持ちます。
ざっと説明すると、"/hoge/fuga/{param}"を"/hoge/fuga?param={param}"のように
変換するという処理を行います。
fromの{}で囲まれた部分を、toの{}で囲まれた部分に配置するという感じです。

複数の{}を指定する事も可能です。

"*"を指定することも可能なのですが、今回はこの"*"の扱いにはまりました。
読書会での結論は"*"を使うと、それ以降は全てひとつのパラメータに格納され、
URLがエンコードされるようになるということでした。
また、{}の中に"*"を使うことはできません。

最後に、前回のMockXXX.javaもそうだったのですが、Router関連も
先頭にコメントがあって、CopyRightはSeasar Foundationになっていました。
Apache Licenceが適用されるようです。
(単純にSeasarからコピーしたということなのでしょうか?)

今回でひとまず、Slim3 Source Code Readingは最終回となりました。
振り返り、まとめの記事は後で、別途残そうと思いますが、
約3カ月、全12回の長い活動になりました。
最初から最後まで読み進めた結果、ある程度はSlim3でGAE/Jの
アプリケーションが作れるようになったような気がします。

今後、GAE/JのSDKのバージョンアップに合わせて
Slim3のバージョンアップも行われると思いますので、
新しくなった時に、また個人的にか、イベントとしてやるかは、
わかりませんが、再度読み進めてみたいと思います。


2012年3月7日水曜日

Slim3 Source Code Reading #11

昨日、Slim3 Source Code Reading #11でした。

今回もtesterパッケージを読みました。
今回はMockXXX.javaとHeaderUtil.java、UrlFetchHandler.javaでした。

MockXXX.javaに関しては、リクエスト、レスポンスを擬似的に再現するための
もので、中身もgetter/setterばかりで特にこれといったものはありませんでした。

ただし、MockHttpServletRequestに関してはHeaderなども変更できるようなので、
例えばjsonのリクエストをテストする場合は事前にHeaderを書き換えた状態で
テストを行うことも可能な感じがしました。
後は、cron jobの実行だとヘッダに”X-AppEngine-Cron: true”が付く事に
なっていますから、手動で実行した時は無視する処理をいれている場合は
Controllerの実行前にHeaderを追加してからテストするという事も
可能だと思います。

HeaderUtilも時刻のフォーマットが定義されているだけで、これも直接利用する事は
無さそうです。

UrlFetchHandlerはinterfaceで、中身を自分で実装する必要があります。
Slim3Demoにもこのクラスを使ったDemoは存在していないようなので、
そもそも利用していないのか?という話になりました。

本題に移ると、これは実際に動作の確認はしていませんが、
アプリケーションでGoogle App EngineのURL Fetch APIを利用した時の
外部サイトのテストで使うために利用するようです。
interfaceを実装する形になりますから、テストデータは自由に作ることができます。

恐らく、以下のようなテストコードになると思います。※以下のコードは未検証です。

@Test
public void URLFetchAPIを使うコントローラのテスト() throws Exception {
    tester.setUrlFetchHandler(new HogeUrlFetchHandler());

    tester.start("/urlfetchsample/");
    AddController controller = tester.getController();
    assertThat(controller, is(notNullValue()));
    assertThat(tester.isRedirect(), is(true));
    assertThat(tester.getDestinationPath(), is("/"));
}

URL Fetch API用のHandlerオブジェクト ※以下のコードは未検証です。
public class HogeUrlFetchHandler() implements UrlFetchHandler {
    public byte[] getContent(URLFetchRequest request) throws IOException {
        String strResponse = "何かのデータ";

        return strResponse.getBytes("UTF-8");
    }

    public int getStatusCode(URLFetchRequest request) throws IOException {
         return HttpServletResponse.SC_OK;
    }
}

昨日の段階では「これを利用する事はあるかな?」という話になりましたが、
既に動作中のWebAPIでテストを行う場合は、URLFetchHandlerは
不要なのかもしれませんが、
動作中のAPIだと実行時点でデータが変わったりするのでテストの結果が
実行するたびに変わってしまうという結果に陥りそうなので
作るべきかな?と思ったりしています。

あとは、WebAPIも作成途中でテストデータが無いと言った時も利用することに
なるでしょう。

今回は中身も大した内容ではなかったので、テストコードをどれくらい書くか?
みたいな話題になりました。
(読書会での結論は「不安な所をテストする」と言われているので、「不安がなくなるまで書け」ば良いようです。)

さて、次回(#12)はorg.slim3.controller.routerを読んで、最終回となります。
ご興味がありましたら、ご参加下さい。