2012年12月30日日曜日

GDG中国のイベント主催者が勉強会について考える

以下のBlog記事が世間を賑わしていて、活発な意見が交換されたようですね。

勉強会なんてやらなくてもいい

何のために勉強会をするのか?

「勉強会なんてやらなくてもいい」へのコメントまとめ(Togetter)

私もGDG中国というコミュニティを運営する者として感じるものもありましたし、2年6ヶ月という長期間の活動になっていますから、改めて考えるいいきっかけだったように思います。

ただ、私の意見からすれば、「勉強会なんてやらなくてもいい」というのは、全くその通り。だと思います。「やりたいからやる。やりたくなければやらなければいい」という事ですね。これに関しては共感もできますし、私も、基本的にそのスタイルでやっています。気合を入れすぎても長く続きませんし、疲れますから。
しかも、報酬があるわけではない、有志の活動なわけですから、そもそも開催される事自体に感謝するべきだと思います。イベントについて文句がある人は、自分で開催すればいいのです。そうすれば、「自分が必要としているイベント」に出会うことができます。アプリケーションをリリースして儲けようとすることと同じように、同じ要望がある方は、必ずどこかにいるので、告知さえすれば参加していただけるのではないでしょうか。
(ちなみに、GDG中国は岡山でのイベントに関しては、私が会場費を全額支払っていますから、毎回赤字です。広島など他県で行う時は、大学の講義室などをお借りして開催していますから、基本的に無料で行えています。場所を提供してくださっている先生方には大変感謝をしています。場所が無ければ、勉強会のイベントは開催できませんから)
参加者も行きたくなければ(魅力がなければ)、無理に参加する必要も無いと思っています。個人的には週末の休みを潰してまで勉強する以外にもやることはあるんじゃないのか?(家族と一緒にいる時間とか、彼氏彼女とのデートでも良いですが。)という事は思ったりします。
そういう貴重な時間よりも優先して来ていただいている事に対して主催者として感謝しなければいけないと思います。

さて、本題から逸れてしまいますが、私が考えるイベントについて吐き出してみたいと思います。

イベント開催に必要なもの。それは何か?


  1. 主催者
  2. 場所
  3. 講師
  4. 参加者

上記の4つ。どれかが欠けてもイベントとして成立しません。ただし、講師は最悪、主催者が兼ねることができますし、場所も(私のように自腹ででも)主催者が用意すればなんとでもなります。
ですが、毎回主催者=講師なんてやってたら、ネタ切れは起こりますし、負荷がかなり高くなってしまいます。なので、GDG中国では講師の方を私以外の方にお願いをしています。無茶なお願いを快く引き受けていただいて、毎回本当に感謝しています。(中にはお断りされる方もいらっしゃるのですが、そういう方にはオファーを控えるようにしています。ある程度、イベントに何度か来ていただいていたり、仲が良くなった方など選別もしています。)

そして、重要なのが「同じ人に連続でオファーをしない」ということをしています。これは講師に対する負荷が高くなるのを防ぐと同時に、幅広い話題の提供(というか、私が聞きたいだけ)と、マンネリ化を防ぐというのもありますし、なにより、”講師の方の練習”の場としても利用していただきたいからです。
そして、練習の場なので、Ustreamや、Hangoutで外部に公開するという事はしません。初めての方に対して、プレッシャーをかけてしまってもいけないので。
また、自分でコミュニティを運営している方もできる限り、オファーを回避しています。それは、私と同じように自分のコミュニティで発表するなど、負荷が高くなる事が多いだろうと想定しているからです。

おかげで、いろいろな方に講師を担当して頂きました。

(2012年開催のイベントから抜粋)
塩入さん
古谷さん(2回担当していただきました!)
+Shingo Ishimuraさん

a-knowさんこと井上さん
+Yusuke Satoさん

上本さん
Google Apps API Japan Groupの皆さん

+Tam Tさん
+Takeshi Furusatoさん

無茶ぶりだったにも関わらず、appengineネタで発表していただいた今岡さん。

そして、2度も関東から来ていただいた、Developer Expertの+Shinichi Ogawaさん。

と、2012年の抜粋でも11人もの方がGDG中国のために(?)講師を担当していただいて、
大変感謝しています。
来年以降も、新たなチャレンジをお待ちしています。

私はというか、GDG中国では、決して「上手な講演をして下さい」なんて言っていませんし、
時間が超過した所で文句も言いませんし、Google技術がからんでいればやりたい放題していただいてかまわない方針にしています。いろんな文句が出るなど、責任はマネージャーの私が取れば良いだけなので。
(自分で開催したイベントなので、自分で責任を取るのは自然なことですから)
”文句”ではなく、”意見”であれば、それは貴重なものなのでフィードバックするだけですね。イベント自体良いものにしたいので。

そして、最後に参加者。
参加者の方がGDG中国のイベントに賛同して、参加していただけなかったら、主催者の私しかいないイベントになってしまいます。1人でも来ていただいたら、それで成功ですよね。参加者0人なら「仕方がない」ぐらいの気分でやらないと主催するのはキツイと思います。
なので、参加していただいている方にも、最大限の気を使っている(のか?)ように思います。

したがって、私にとって、講師を担当している方も、参加者も感謝されるべきものなので、
「主催者がえらい」とか「スタッフがえらい」という事は考えてもいませんし、全てが平等に感謝されるべきだと思います。
先日行われた、忘年会議2012に参加させていただきましたが、その中の催しの中に
「イベント主催者を表彰する」という主旨のものがありました。
私もリストに入っていたのですが、私は上記の理由から辞退させていただきました。
(昨年は受賞したのですが、その時はGo本の執筆も含まれていたので。)

そして、「目的」という点での話をすると、GDG中国の運営を始めた理由は、「私がGoogle技術+Webの技術を勉強したかった」から。なんですね。イベントにすれば、私が知らないことを他の人が話してくれるかもしれないし、新たなGoogle技術好きの技術者と知り合えて、情報交換ができるかもしれない。私がすべて調べて発表するだけでも、新たな知見は増えるでしょうが、範囲として私が知っている範囲のものが多くなってしまうと思います。
そして、「都会にはたくさん素晴らしい技術者がいる」ということに対して、疑問を持っていて、「ローカル(身近)な所にも必ずいるだろう」ということを考えています。また、「新たな技術者を素晴らしい技術者に成長させる」という事も考えるようになりました。「ないなら作る」というソフトウェアならではの考え方ですね。
毎回遠くから来ていただく、という事は基本的に無理ですし、その前提で運営はできないと思います。(最近はGoogleさんのありがたいサポートがあったりもするのですが…)
「世界」だの「日本」だのと、でかいスケールの話をしていても、自分の身近に誰もいない(とまでは言いませんが)という雰囲気もあれだし。。

「身近にいる技術者」がGDG中国に来てくれて、参加者が増えるなら、Google I/Oなどにも行ってみたいですが、サンフランシスコに行っても、あまり増えないだろうと思います。(一番のポイントは「資金」ですけどね!)私は、グッズが欲しいわけでもないですし、最新情報も、少しのタイムラグで公開されるわけで、外国の方とコミュニケーションを取る事以外での利点が見つけられていません。

また、グッズのプレゼントについて。GDGはGoogleのありがたいサポートのおかげでグッズをいただけるのですが、GDG中国では参加者全員に配るという事は、ほとんどしていません。
それのために新たにグッズを要請することもほとんどしていません。
勉強会はプレゼント大会ではないからです。

ただ、講師をしていただいた方には可能な限りお渡しするようにしています。
(報酬のつもりはないのですが…)

最後に、主催者や講師をすることは、私にとっては「大きなチャンス」だと思っています。注目され、信頼される=仕事につながるのかもしれませんし、私自身もイベントを主催しているので(?)、ぎりぎり生き残っているわけですね。大したやつじゃないのに。

おかげで、GDG中国にいたメンバーも東京に引っこ抜かれ晴れて転職でき、最新の技術に触れる事ができるようになりました。
抜けてしまって、戦力ダウンになりますが、彼らにとって良いことなので、活躍を期待しています。

いろいろな人が集まるので、多種多様な考えや、目的が交錯するわけですから、意見がぶつかることもあるかもしれませんが、ゆるくやりましょう。
そして、まだ見ぬ新たな技術者が寄ってくることを期待しています。


2012年を振り返る

もうすぐ2012年が終わりますね。
私はまだ明日の朝も仕事なんですが…。

長文になっていますが、ご興味があればどうぞ。

2012年の目標
http://takashi-yokoyama.blogspot.jp/2012/01/2012.html

ということで、初めてだと思いますが、目標の記事から達成度を振り返ってみたいと思います。

  1. オープンセミナー@広島での発表(1/21(土))
  2. 岡山在住の技術者の方に中国GTUGスタッフのお願いをする。
  3. 第12回勉強会@岡山
オープンセミナー@広島では確かBigTableのQueryについての話をしました。
これはGoをベースに話をしたのでちょっと難しい+興味を持ちづらい内容になってしまったのではないかと思っています。

そして、中国GTUGのスタッフのお願いですが、今は、GDG中国に団体名が変わってしまいましたが岡山のスタッフをしていただくよう、3人にお願いしました。
+Takanobu Hagnioさん、+Shingo Ishimuraさん、+Takeshi Furusatoさんの3人です)

第12回勉強会も無事開催出来ましたし、2011年に確定していたものは全て達成したのではないでしょうか。

次に、GDG中国関連の目標ですね。


  1. Slim3 Source Code Readingの完走
  2. 広島でのイベントの開催
  3. ハッカソン、ハンズオン
  4. Source Code Readingの開催をする。
Slim3 Source Code Readingは結局、私と真さんの2人での開催がほとんどになってしまいましたが、なんとか全パッケージを読み進め、Google App Engineの知識は増えたのではないかと思っています。3ヶ月間の長い活動でしたが、良い時間が過ごせたと思います。

広島でのイベントの開催ですが、6月のGoogle Apps Scriptのハンズオンを行いました。これがきっかけになって、GASの利用者が増えたり、私も新たな技術者との出会いもあって2012年後半の活動への大きな味方が増えました。

ハッカソン、ハンズオンですが、今年はハッカソンを封印して、ハンズオンを多めに行いました。講師の方には本当に感謝しています。技術が浸透して相対的なレベルアップが図れているのかどうかは
確認できませんが、ゆっくりと、確実に浸透してユーザが増えていると信じています。

Source Code Readingの開催…については今年はできませんでした。
当時は普段のイベントとして開催しようと思っていたのだと思いますが、私自身の動きが
悪くなってしまったのもあって、用意がしにくくなっていたのもあります。
また、計画のし直しだと思います。

次は仕事関連ですか。これについては思ったより達成できたなー。という感じです。

  1. 執筆する
  2. GAEアプリの開発
  3. Chrome-Extensionの開発
  4. Androidアプリの開発
  5. バイトする
  6. 何か仕事を受託する
まず、執筆する。来年の1月中(恐らく、下旬かな?)にGoogle Apps Scriptの書籍が出版されます。
というか、Google+では、公表していたのですが、9月から11月にかけて執筆し、12月に校正作業を行っていました。初めて、「リファレンス」本にチャレンジさせて頂きまして、難しさが良くわかりました。
(執筆自体、それなりに計画していたり、ネタを溜めておかないと大変だという事は再度よくわかりました。)

発売を今しばらくお待ち下さい。

GAEアプリケーションの開発という事ですが、仕事で1つ(現在は費用の問題で停止中。再開する可能性はない…。)Androidアプリケーションのサーバサイドで1つ。もっと簡単でいい方法が見つかったので開発を中止しましたが、会計アプリケーションが1つ。
意外と作ったな。という感じですね。

Chrome-Extensionの開発は今年はできませんでした。仕事でも出てきたりしたので、もう少し知識を深めたいところです。

Androidアプリケーションの開発ですが、今年はええことなかったわー(β)をリリースしました。このアプリケーションはいわゆる「愚痴るアプリ」で、匿名ですし、投稿を見ている人の反応は2つしかできないというシンプルなものです。意外にも高評価を得られていて、後はユーザの数が増えていけばいいのになー。という感じです。他にも既存アプリケーションのメンテナンスを行っています。
2012年後半は、後述しますがバイトを始めたこともあったり、仕事が一気に集まってきたのもあって
開発が思うように進んでいません。

バイトに関しては、2012年は「食品運送のトラック運転手」のアルバイトをしています。
早朝から午前中にかけての仕事で、専門学校の授業が減ることもあって、5月ぐらいまで
仕事が入るまで粘っていたのですが、GAEアプリケーションの仕事で思うように売上が上がらず
どうしようもなかったので、バイトをする。という選択をしました。
午後に開発の仕事をするために午前中の仕事を選んだのですが、意外と肉体労働は
地味に体力を奪われます。何個か仕事が重なると辛いですね。
(辛いのはバイトではなく、午後の仕事w)

何か仕事を受託することに関しては、思いがけないチャンスをいただきまして、
作業時間が取れない制約があったので
受託ではないのですが、Google Appsのアドバイザーの仕事をしています。
(最初は、アドバイザーの依頼だったのですが、現時点では、API調査の作業をしていますが…)
これにより、Google Appsとその周りのAPIの扱い方について、もっと勉強しないといけないなと
思いました。特に、中国地方だと、Google Appsに詳しい方がいるという事を聞かないので
発掘もしたいですし、いないなら私がサポートできたら…。と勝手に考えています。
来年の1月末まで、作業を行う予定になっています。

最後に生活面ですが、

  1. スポーツクラブに通う
  2. Google+を使う
スポーツクラブは11月〜12月にかけては仕事が重なったりして回数が減りましたが、
十分行けて、元は確実に取れたと確信しています!

それから、重要なのは、Google+を使う。ということ。これはいい選択でした。
以前からTwitter/Facebook/Google+のどれがいいのか、という事を考えていました。
私にはTwitterが絶妙に使いにくかったので、
(フォローしていない人のコメント、やり取り(経緯)が見えない。興味をもったコメントに対してMensionを飛ばすと、それまでのやりとりがあった時に「えっ?」と反応になる。文字数の制限によって、
正しく意思が伝わりにくいなど。あとは、フォロワーを増やすとタイムラインが流れすぎて読めないとか)
Google+がない時はFacebookだな。と思っていたのですが、Facebookは最近ユーザが増えてきたこともあって、学生時代の(交流がなかった)同級生などが友達申請してきたりなど、
「きもちわるい」ことこの上ないので、近づきたくない。
ということで、Google+を使ってみようと思ったことと、「使わないと良さは伝えられない」という
考えもあったので1年間じっくり使ってみました。

それまでは、Twitterでコミュニケーションを取っている方もいたので、距離を置かないといけなくなってしまうことに迷いもありましたが、どうしても移行しようと思うと回避できないことだったので、
覚悟を決めて使っています。

Twitter、Facebookそれぞれにある、利点が組み込まれているので、私にとっては今の所
ベストな選択となったように思っています。これからも使います。
Twitterはだんだんと使わなくなっていく(今年はROM専+発信のみでした。来年は発信のみになるかもしれません)だろうと思っています。

ということで、目標達成率は
3/3、3/4、5/6ということで、11/13(84.6%)で、あまりいい年ではなかったと思っていましたが意外と目標が達成できていたという事実に驚いています。

来年のことは来年考えるという事にして、今年のまとめでした!



2012年8月2日木曜日

GAE/GでGoogle Cloud Storageを利用するには(1)

いろいろ都合があって、GAE/Gから、Google Cloud Storageを利用してみたので
利用方法をメモしておきます。
GAE/GのFileServiceはなぜか、ドキュメントに記載されていない(8/2現在)ので、
GAE/GのSDKのソースコードを見て使い方を理解する必要があります。
(もしくは、Python版、Java版のプログラムから推測するとか…)
ということで、時間がある時にいじって、Blogにメモしようと思います。

1. Google API Consoleで課金設定をONにする。

Google Cloud Storageは課金設定をONにしないと、書き込みも読み込みも、Bucketの作成さえもできないようなので、課金をONにします。

課金をONにしないとIO_ERRORになってしまいます。

※ローカル環境では、何もしなくても実行可能なので、ローカルでしか動かさないという人は、この手順は不要です。

2. Google APIs ConsoleのTeamにサービスのアカウントを追加する。

GAEのアプリケーションからアクセスさせるためには、API Consoleにサービスのアカウントを追加しなければPERMISSION_DENIEDでエラーになります。

サービスのアカウントの場所は、http://appengine.google.comのGAEのアプリケーションにある、
Application→Application Settingsを開きBasic(一番上)のService Account Nameにあるので、書かれているアカウントをコピーします。

[app-id]@appspot.gserviceaccount.com

みたいなものが、書かれていると思います。
(app-idは作成したappidが書かれています。)

Google APIs ConsoleのTeamのところに、ペーストして、権限設定をして追加すれば完了です。


※ローカル環境では、何もしなくても実行可能なので、ローカルでしか動かさないという人は、この手順は不要です。


3. Go言語でプログラムを作成し、デプロイする。

package controller

import (
 "appengine"
 "appengine/file"
 "fmt"
 "net/http"
)

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

func list(w http.ResponseWriter, r *http.Request) {
 c := appengine.NewContext(r)

 name, err := file.DefaultBucketName(c); 
 if err != nil {
  http.Error(w, err.Error(), http.StatusInternalServerError)
 } else {
  fmt.Fprintf(w, "DefaultBucketName = %s", name)
 }

 options := new(file.CreateOptions)
 options.MIMEType = "text/plain"
 options.BucketName = "yourBucketName"

// if wc, absFileName, cErr := file.Create(c, "/gs/yourBucketName/test/sample.txt", options); cErr != nil {
 if wc, absFileName, cErr := file.Create(c, "test/sample.txt", options); cErr != nil {
  fmt.Fprintf(w, "Create Error")
  fmt.Fprintf(w, " %s", absFileName)
  c.Debugf("Create Error %s [%s]", cErr.Error(), absFileName)
 } else {
  fmt.Fprintf(w, "Create Success")
  fmt.Fprintf(w, " %s", absFileName)

  if _, wErr := wc.Write([]byte("Sample Text")); wErr != nil {
   c.Debugf("Write Error = %s", wErr.Error())
  }

  wc.Close()

  if fi, statErr := file.Stat(c, absFileName); statErr != nil {
   c.Debugf("%s", statErr.Error())
  } else {
   c.Debugf("%s %d", fi.Name(), fi.Size())
  }
 }

}

4. 実行

上記のプログラムを実行すると、作成したBucketの中のtestフォルダの中にsample.txtが
作成されていると思います。

GAE/Gだと、file.CreateOptionsにBucketNameが指定されていないと、デフォルトの
BucketNameが設定される様になっているのですが、本番環境では、""(空文字列)なので
作成時にエラーになると思います。(BucketNameは必ず指定する必要があるので)
(ちなみに、ローカル環境だと"app_default_bucket"というのが設定されます)

後は、プログラムを見れば大体わかると思いますが、ファイル作成時は、絶対パス指定でも、
相対パス指定でもファイルの参照は可能です。

絶対パス指定の場合は、先頭に"/gs"をつける必要があります。
書式は「/gs/[Bucket名]/フォルダ名/ファイル名」となります。
フォルダ名を省略すれば、Bucketの直下(という表現が正しいのか?)に作成されると思います。

ということで、課金が恐ろしいですが、
1GBあたり、$0.12とか、$0.01とか、ある程度のところまでは、円高のおかげでうまい棒程度で済みそうなので
いろいろいじってみたいと思います。
(参考:https://developers.google.com/storage/docs/pricingandterms?hl=ja


2012年8月1日水曜日

Google Apps ScriptでGoogle DriveのファイルリストをDashBoardに表示する

Google Apps ScriptのTutorialに、DashBoardのチュートリアルがあったので、
サンプルを入力して実行してみました。

簡単にデータの一覧と円グラフが表示できて、しかも、フィルタもつけることができます。
DashBoardの機能を使うことで、いろいろなデータを解析するのに役に立つかもしれません。

サンプルを元に、Google Driveにある資料のリストを取得して、リストに出す。

というのを作ってみました。
ソースコードを添付しておきます。

function doGet() {
  // Google Driveのリストを取得し、DataTableを生成
  var files = DocsList.getFiles();
  
  var dataTable = Charts.newDataTable();
  dataTable.addColumn(Charts.ColumnType.STRING, "FileType");
  dataTable.addColumn(Charts.ColumnType.NUMBER, "Size");
  dataTable.addColumn(Charts.ColumnType.DATE, "Create");
  dataTable.addColumn(Charts.ColumnType.DATE, "LastUpdate");
  
  for(var i in files) {
    var row = [];
    row.push(files[i].getFileType().toString());
    row.push(Number(files[i].getSize()));
    row.push(files[i].getDateCreated());
    row.push(files[i].getLastUpdated());

    dataTable.addRow(row);
  }
  var data = dataTable.build();
  
  // FileTypeでのフィルタ
  var typeFilter = Charts.newCategoryFilter()
      .setFilterColumnLabel("FileType")
      .build();
    
  var tableChart = Charts.newTableChart().build();
  
  var dashboard = Charts.newDashboardPanel()
      .setDataTable(data)
      .bind(typeFilter, tableChart)
      .build();
  
  var app = UiApp.createApplication();
  dashboard.add(
    app.createVerticalPanel()
    .add(typeFilter)
    .add(tableChart)
    .setSpacing(30)
  );
  
  app.add(dashboard);
  
  return app;
}

サンプルでは、日付の列がなかったので、今回は作成日と最終更新日を取得するようにしています。
また、データが定数だったので、このソースコードでは、データを動的に追加するようにもしています。
(定数での挙動と、メソッドの戻り値での挙動で少しハマったので、気をつけて下さい。)

日付の列に関しては、Date型のオブジェクトを渡せば良いので、そんなに難しくはないですが、
文字列と、数値に関しては、それぞれ、StringとNumberに変換する必要があるので
その対応が必要になります。
(定数だと、暗黙型変換でいい感じになるみたい)

オブジェクトだと、Object.toString()を使い、数値だとNumber()を使って型を変換しておきます。
私はこの型変換ではまりました。

今のところ、日付によるフィルタというのは存在しない(?)みたいです。
本当は、期間別のデータを表示してみたりしたかったのですが…。

他にもいろんなグラフが表示できるので試してみて下さい。

2012年7月31日火曜日

Google Apps Scriptで連絡先を編集する

Google Apps Scriptで連絡先(Contacts)を編集する方法をメモしておきます。
  • 連絡先を作る
var contact = ContactsApp.createContact("名前", "苗字", "target@youraddress.com");

これだけだと、インスタンスが生成されるだけで、実際に連絡先が
登録されるわけではないので注意です。
実際に登録するには、ContactsApp.addContacts()を呼び出さないといけません。

  • 連絡先のグループを作る
var contactgroup = ContactsApp.createContactGroup("Sample Group");

連絡先のグループを新規に作成します。

  • 連絡先のグループを取得する
var contactgroup = ContactsApp.getContactGroup("Sample Group");

連絡先のグループを名前から検索して取得します。

  • システム標準のグループを取得する
var myContacts = ContactsApp.getContactGroup("System Group: My Contacts");

グループ名に"System Group: "をつけるようです。

ちなみに、
getContactGroup()は、引数の名前のグループが存在しない時はnullを返してくるようです。
getContactGroupById()を使えば、ContactGroupのIDを指定してグループを取得できます。
  • グループを取得する
var contactgroups = ContactsApp.getContactGroups();
for(var i in contactgroups) {
  Logger.log(contactgroups[i].getGroupName());
}

getContactsGroups()を呼び出すと、登録されているグループが取得されます。
戻り値は配列です。標準のグループも取得されるので、システム標準かどうかは
ログに出力すれば、判断できると思います。

  • 連絡先をメールアドレスから検索して取得する。

var contact = ContactsApp.findByEmailAddress('target@youraddress.com');
var contact = ContactsApp.getContact('target@youraddress.com);

注意点としては、リテラル文字列を指定する時は''(シングルクオート)を使わないと
正しく動作しないようです。
  • 連絡先、グループを削除する

連絡先を削除するには、ContactsApp.deleteContact(contact)
グループを削除するにはContactsApp.deleteContactGroup(group)を
呼び出せば削除可能です。


2012年7月25日水曜日

Go言語のWebフレームワーク"goweb"をGAE/Gで動かす

昨日、公開された(?)Go言語のWebフレームワークの"goweb"を早速GAE/Gで動作するか
試してみました。

導入方法は、以下のように行います。

  1. gowebのソースコードをダウンロードする。
    1. go get code.google.com/p/goweb/goweb
    2. Google Codeからダウンロードする。
    • 1の方法を使った場合は、$GOPATH配下か、GOPATHがなければ、Go言語のコマンドがあるディレクトリにダウンロードされます。
    • ダウンロードを行った場合は、任意のディレクトリに展開して下さい。
  2. ダウンロード後展開 or go getで取得したディレクトリ配下のgowebディレクトリをGAE/Gのアプリケーションディレクトリにコピーする。(GAE/Gの場合は、GOPATH変数を認識しないので、手動でコピーしておく必要があります)
  3. 以下のようなプログラムを作成し、実行する。

package testapp

import (
 "appengine"
 "goweb"
 "fmt"
 "net/http"
)

type PeopleController struct {}
func (p *PeopleController) HandleRequest(c *goweb.Context) {
 context := appengine.NewContext(c.Request)
 context.Debugf("PeopleController#HandleRequest Called.")
 fmt.Fprintf(c.ResponseWriter, "You are looking for person with ID %s", c.PathParams["id"])
 fmt.Fprintf(c.ResponseWriter, "\nServerSoftware = %s", appengine.ServerSoftware())
}

func init() {

 var peopleController *PeopleController = new(PeopleController)
 goweb.Map("/people/{id}", peopleController)

 goweb.ConfigureDefaultFormatters()
 http.Handle("/", goweb.DefaultHttpHandler)
}

gowebパッケージはapp.yamlがあるディレクトリにコピーしておけば良いです。

URLは、http://localhost:8080/people/testみたいな感じにすると、結果が返ってくる事が
わかると思います。

また、appengineのAPIを実行する場合にappengine.Contextを取得したい時は、
HandleRequestメソッドの引数cからappengine.Contextを生成できます。

context := appengine.NewContext(c.Request)

ソースコードもこれから読んでみようと思っていますが、フレームワークを利用せずに
作成するよりはだいぶ楽かもしれないですね。

ちなみに、gowebのディレクトリの中に、goweb_testappディレクトリがあって、その中には
以前のGAE/Gのバージョンで試していた形跡があります。
api_version: 2なので、1.5.3以前かな?


2012年7月16日月曜日

Ubuntu 12.04にgolangを”ソースから”インストールする。

久しぶりにデスクトップPCを使おうと思ったので、
眠っていたPC(Ubuntu11.10→12.04にアップグレード)に

golangの環境をインストールしました。

ちなみに、ソースからのインストールです。

以下が手順です。

1. まず、コンパイルのための環境をインストール
$ sudo apt-get install gcc libc6-dev

2. Go言語のソースコードはMercurialで管理されているので、コマンドをインストール。
$ sudo apt-get install mercurial

3. 環境変数の設定
export GOROOT=[ソースコードがあるディレクトリ]
export PATH=$PATH:$GOROOT/bin

4. Go言語のコンパイル
$ cd $GOROOT/src
$ ./all.bash

5. Go言語コンパイラの動作確認

$ go version
go version go1.0.2(という感じの出力があればOK)

package main

func main() {
    println("Hello World")
}

とりあえず、Hello Worldを出力する、適当なサンプルを作って。

$ go run hello.go
Hello World

となれば、成功です。

以前の知識と変わっている(?)点

  1. $GOARCHがなくてもgolangのコンパイルが可能。(つけたらつけたで何か変化するかな?)

ということで、Let's try golang!!

そして、Enjoy golang!!


2012年7月12日木曜日

DevFestX Japan 2012 Summerの中国会場を担当しました。

7/8(日)にDevFestX Japan 2012 Summerが開催されました。
参加者の皆様、スタッフ、協力してくれた福山のみなさん
お疲れさまでした。そしてありがとうございました。
今後もよろしくお願いします。

会場の様子は中国GTUGのサイトに写真を掲載していますので
ごらんください。

そこで、中国GTUGの名前が、GDG中国に変更される事が発表されました。
他にもGoogle Developersブランドのいろんなサービス、イベント、コミュニティが
新設されています。
(詳しくは、DevFestX Japan 2012 Summerの講演スライドをご覧ください)

日本だけ存在していた「Google API Expert」が世界中で認定されることになり、
名称も「Google Developer Expert」に変わっています。
中国会場には、小川真一(@shin1ogawa)さんに来ていただいて、
いろいろな話が聞けて良かったと思います。
いい刺激にもなりました。

本当は、イベント中の講演をじっくり聞きたいところだったのですが、
主催者のジレンマ(?)として、会場の空気、状況、その他色々な対応を
するために、話をほとんど聞けない(聞いているが、頭には残らない)ので、
後で、Google I/Oの動画(英語)をじっくり見ようと思います。

小川さんと話をしている中で、印象に残ったのが、
「おがわさんに質問する97のこと」について、

回答が

  1. 97も質問するな
  2. ググレカス(これは、私が言ったか)
  3. ソース読め

ものすごく、厳しい正しい回答で、面白かったと思います。

後は、「GAEはGoが最強」というお言葉が聞けて、大満足しております。
が、ネックは「テスト」(これも私も同じ意見でした。テストができるから
仕事では、Javaを選択してます)
※Python版のテストはどうしているんだろうか。小川さんに聞くのを忘れていた。

脱線すると、Go言語にもgo testという機能があって、
言語自体にテスティングフレームワークが含まれているのですが、
パッケージのテストしかできないことと、GAE/GのSDKのgoコマンドに
パスを通して、go testを実行し、仮に動作したとして、GAEのAPIを
実行してくれるのかどうかがよくわかっていない(どこかで試してみないとなー
と思っています。)ですが、試してみる価値はあるかもしれません。

あと、「appengine ja nightを開催してくれれば、(参加者が20名以上であれば)また来れる」というありがたいお言葉を頂いたので、今は西は京都までなので、
是非、中国地方でも開催を!と言われました。
やっぱり、「night」だから、夜やるのかな…?

さて、運営サイドの話題に戻すと

中国会場の参加者は、事前申し込み27人/当日参加24人/懇親会参加17人と
いう結果でした。
今回は前日に岡山で大きなイベントがあったこともあり、岡山での告知は
少し抑えました(週末両方をイベント参加にするのは大変だと思ったので)

また、本当は岡山で開催かな?と思っていたのですが
条件に合う場所が空いておらず、たまたま、福山大学の先生から別件の用事を
頼まれたので、交換条件で、ついでに会場をお借りしました。

会場が福山に決まったので、目的としては

  1. 福山の技術者の発掘
  2. 勉強会等のスタートアップ
  3. 会場の選択肢を増やす
  4. (会場を借りた見返りとして)福山大学の宣伝

と言った、目的をもって準備を進めました。

実際には広島(市内)、岡山、東京の方、福山大学の生徒が大半で、
技術者の参加が少なかったので、残念な点でした。
原因は、告知が行き届かなかったのか、曜日が悪かったのか。。
(土曜日開催の方が、傾向として良いみたい(?))

当日は、事前テストではうまくいっていた機材のトラブルがあり、
バックアップ担当の松原さんの機材を借りました。
そして、Webカメラも6/23のイベントでは超安定していたのに、
なぜか映像が白くなって、使えなかったので、小川さんが持参した
Webカメラを使わせていただきました。
ネットワークに関しては超安定していたので、中国会場で
回線がおちるなどの現象には遭遇しませんでした。
アンケートに関しても、全体的に満足していただいたようで、
大成功だったと思っています。

良い点、悪い点色々ありましたが、今後のイベントにつながれば
良いのではないかと思っています。

福山の技術者の盛り上がりに関しては、福山のポテンシャルに
期待しています。

2012年7月11日水曜日

第14回勉強会@広島を開催しました。

blogに残すのが、だいぶ遅くなってしまいましたが、
第14回中国GTUG勉強会を広島で開催してきました。

今回は「Google Docs」をテーマに
.NET勉強会/ヒーロー島の上本さんにGoogle DocsとMicrosoft Office Web Appsを
比較していただく話題を展開していただきました。

ポイントを整理すると、
  • Google Apps Scriptはすばらしい。
  • Googleの技術はAPIが充実している
  • Microsoft Officeの方もそのうちマクロ機能は実装されるだろう。
という話がありました。Google DocsもMicrosoft Office Web Appsも
双方に利点があるし、今後の機能拡張に注目ではないでしょうか。

第14回の勉強会では、Hangoutを使って、東京のGoogle Apps API Japan Group
皆さんとつないで、ハンズオンにチューターとして協力していただいたり、
GASを利用してできることを発表していただきました。
(夜を徹して資料を作成していただいた(?)みたいで、本当にありがとうございます)

さて、今回は私がハンズオンの講師として
「イベント管理ツール」をテーマにして、メールの送受信、トリガーあたりを
使ってみました。
率直な感想として、「GASは遅い」というイメージがついてしまったかな?と
思ったり思わなかったり。
(実際、遅延したのが原因かわからないが、ソースコードが反映されずにうまく動作しないということもあって、フォローが大変でした。)

で、現場にいた私も感じていましたが、参加者の皆さんが疲れてしまって、
「詰め込みすぎ」感がでてしまったかな?と思います。
(実際にそういう意見も聞こえました)

Go言語のハンズオン(第13回勉強会)の時は「シナリオ」がなかったので、
置き去り感がでてしまったので、シナリオを重視したのですが、
ちょっと、ボリュームが多かった(休憩が少なかった?)ようです。。
やっぱりハンズオンは難しい。

当日の資料に関しては、中国GTUGのサイトを見て頂ければ公開してありますので
参考にしてみてください。

Google Apps ScriptもGoogle I/Oでだいぶ進化したので、
新しく追加された機能を利用するか、これまでの機能について
紹介するか。
イベントを行う上での幅は広がったかな?と思っています。

Google Apps Scriptは無料だし、質問に答えてくれる方もたくさんいらっしゃるので
Twitter / Google+どちらでも、#gasjaを付けて質問をすれば
困ることはないと思います。

2012年6月5日火曜日

GAE/Gで時間のチェック(Datastore編)

GAE/GのSDK 1.6.4からGo Ver 1が適用され、
時間をデータストアに保存する時は、datastore.Time(int64のtype。今は削除されている)では無く、time.Timeが利用できるようになっています。(参考Blog記事

time.Timeをデータストアに格納しようと思うと、普通に日時が設定されていれば問題ないのですが、
例えば、データストアのプロパティの追加した時など、日時のプロパティに時間が設定されていない時があります。

日時が設定されていないまま、Putを行うと、"time value out of range"のエラーが
発生してしまいます。

これを回避するためには、

  1. PropertyLoadSaverのLoadかSaveで日付をセットする。
  2. リリース前にデータの移行をしておく。(未設定の日時が発生しないようにしておく)
のどちらかになると思います。2のケースだと全件Putされるので問題ないと思いますが、
移行プログラムでデータを移行させない事の方が多いと思います。

したがって、PropertyLoadSaverインターフェイスを実装して、以下のように、
時間がZeroかどうかを判定するロジックを入れておかないと、Putが成功しません。
判定メソッドはGo言語標準で用意されているIsZero()を利用します。
LoadとSaveどちらで設定するのも構わないと思いますが、ここでは、Load時に設定する
方針にしてみます。(if文1つなら処理コストはあまり変わらないだろうと言っても、読み込み時に毎回実行するのは無駄だ。と思われる場合はSaveで実装するのが良いでしょう)

func(hoge *Hoge) Load(c <-chan datastore.Property) error {
        // データストアから読み出し。
 if err := datastore.LoadStruct(hoge, c); err != nil {
  return err
 }

 if hoge.Date.IsZero() {
  // データがない(0000/01/01状態)ものは現在時刻にするなど。
  hoge.Date = time.Now().Format(TimeFormat)
 }

 return nil
}

データが既に入っている場合はIsZero()がfalseになると思われるので、
特に時間の設定は不要(データストアから読みだした時点で代入されている)でしょう。


GAE/GでバッチGetをした時の注意点

最近、Androidアプリ「ええことなかったわー(β)」を開発したのですが、
実は、データを貯めているサーバ側はGAE/Gで開発しています。

本当に、レスポンスとしては早いですよ。
今のところ、リクエストに対するレスポンスは、
ms単位の時間でレスポンスを返しています。
(GAE/Jでも仕事でサービスを利用していますが、レスポンスの内容が全然違いますが、1s前後のオーダだったりするので…)

先ほど、Admin ConsoleでのPerformanceを最弱にしていなかったので、
まだユーザ数も少ないので、最弱(Max Idle Instance = 1+Min Pending Latency = automatic)にしてみました。その結果は後日…ということで。

さて、本題のバッチGetを利用するときの注意点なのですが、
結論から言うと、「指定したキーのエンティティが1件でも無いと、appengine.MultiErrorを返す」ということです。
実際には、以下のようなコードになると思いますが

data := make([]HogeEntity, len(keys))
if err := datastore.GetMulti(c, keys, data); err != nil {
// 何かのエラー
}

全てのキーのエンティティがあれば、エラーの判定にならない(戻り値=nil)なのですが、
1つでもエンティティが存在しない場合、全てエラー(戻り値 = appengine.MultiError)となります。
(おかげで、全部エラーレスポンスに…)

で、これは困ったという事で、以下のようなチェックを行うことになります。

data := make([]HogeEntity, len(keys))
if err := datastore.GetMulti(c, keys, data); err != nil {
 if _, ok := err.(appengine.MultiError); ok {
  // バッチGetだと指定したキーのエンティティが1個でもない場合、MultiErrorが返る。
  c.Infof("Hoge NoSuchEntity")
  return keys, data, nil
 } else {
  c.Errorf("Batch Get Error %s", err.Error())
  return keys, nil, err   
 }
}

実際、appengine.MultiErrorが返されても、存在しているデータは取得できるので
取得したデータが構造体にセットされている部分と、空の部分がsliceに存在する
状態になるので、後で、空の部分を除外するなど、編集処理が必要となります。

ちなみに、appengine.MultiErrorはtype MultiError []errorと定義されていますので
複数のエラーが含まれた状態になることも考慮しておく方が良いかもしれません。
私はGetだけの利用だったので、今のところ、正常なものは利用しています。

MultiErrorの戻り値はGetMulti/PutMulti/DeleteMultiで戻される事があるという
ことなので、(ドキュメントのBasic Operationsに明記)利用するときはappengine.MultiErrorの中身を確認してみてください。

2012年5月15日火曜日

BloggerのRSSフィード

本当はこの記事を書く予定は無かったのですが、
ふと思い出したので調べてみました。

基本的にBloggerのRSSフィードは
Atom1.0: http://blogname.blogspot.jp/feeds/posts/default
RSS 2.0: http://blogname.blogspot.jp/feeds/posts/default?alt=rss

このサイトのフィードだと、「http://takashi-yokoyama.blogspot.jp/feeds/posts/default」という感じです。

で、取得できます。
(ちなみに最後に添付しているヘルプのURLはblogspot.comになっていますが、
日本向け(?)はblogspot.jpとなりますので、気をつけて下さい)

それから、サイト側でのフィードの許可は
管理画面の「設定」→「その他」→サイトフィードの所で設定します。
(多分、何もしなければ、「完全」で設定されていると思われる)

ただ、Google Readerを利用する場合はブラウザのURLをそのまま
コピペで登録すれば購読できるようになったりしますね。
(RSSリーダは、外部サイトとGoogleのサービスが利用できるのであれば、
Google Readerをおすすめします。
購読するRSSを仕事用とPrivate用で分けたい場合などは話は違ってきますが、
(あくまでも、個人的な感覚では)複数の設定を持っててもあまり便利ではないかと思いますので。。)

BloggerのフィードURL
http://support.google.com/blogger/bin/answer.py?hl=ja&answer=97933


2012年4月5日木曜日

BloggerとGoogle+を連動させる。

例えば、Blogを投稿した後、告知とかの目的で
Google+に投稿することがあると思います。

Blogの公開と同時にGoogle+への投稿が自動的にできるようになりました。

方法は、以下の設定をすることで可能になります。

  1. Bloggerのマイブログページの右上にある歯車をクリック
  2. Google+に接続するを選択
  3. BloggerのプロフィールにGoogle+のプロフィールを使うよ?と言われるのでOKする。
  4. 再度ログイン(自動的にさせられる)
  5. Bloggerのマイブログページに移動し、設定  ›  投稿とコメントメニューを選択
  6. Google+に共有の設定が増えているので、「はい」に設定する。
Bloggerヘルプ(投稿をGoogle+で共有する)
http://support.google.com/blogger/bin/answer.py?hl=ja&answer=1752748&ctx=go

そうすると、公開ボタンを押した後、共有する入力欄が出てくるはず!

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を読んで、最終回となります。
ご興味がありましたら、ご参加下さい。

2012年2月29日水曜日

Slim3 Source Code Reading #10

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

前回に引き続き、testerを読み進めました。
今日は、以下のファイルを読みました。

  • ControllerTestCase.java
  • ControllerTester.java
  • ServletTester.java

ControllerTestCase.javaはAppEngineTestCaseと同じように中身は
testerというメンバ変数があるだけでした。ので、スルー。

testerはControllerTesterクラスの変数なので、そのまま、ControllerTester.javaに
進みました。

ControllerTesterはsetUpで、初期化パラメータの読み込みと、
FrontControllerの初期化を行っています。
これが、リクエストを受け取った時のFilterの初期化の代わりとなっているようです。

tearDownは後処理が行われて終わりです。

startメソッドが、実際にControllerを実行するメソッドですが、内部でRouterも
読み取るので、Routerの設定は反映されます。

以下の3メソッドはControllerに合わせてあるのだと思いますが、
中身も少ないので、概要だけ。
getDestinationPathは遷移先のパスを返します。
getControllerはrequestScopeに格納されているControllerを返します。
getErrorsはValidationエラーの情報を取得します。

最後にServletTester.javaですが、
setUpでは、元の設定を退避し、本番環境に合わせるように設定されるようです。
例えば、タイムゾーンは"UTC"をセットしていますし、ロケールはLocale.USがセットされます。

tearDownで、退避していた元の設定を復元しています。

param()はパラメータの取得/設定します。このメソッドを使ってテスト前の
パラメータを指定する事ができます。
paramValues()はパラメータを配列で取得/配列を設定します。

以下のメソッドは型変換を行った後の値を返すメソッドです。
Controllerクラスにもありました。パラメータの型もチェックしたい場合は
利用した方が良いでしょう。
asShort()
asInteger()
asLong()
asFloat()
asDouble()
asString()
asBoolean()
asDate()
asKey()

以下のメソッドは、それぞれのスコープの値を取得/設定します。
requestScope()
sessionScope()
applicationScope()

isRedirect()はリダイレクト先のパスが設定されていればTrueを返します。

今日は、薄い内容で終わるかと思ったのですが、最後の

addBlobKey()でハマりました。
これは、Blobstoreのテストを行うために、実行前にBlobstoreのKeyを設定
しておくものだと思うのですが、どうしても使い方がわからなかったので
slim3Demoのソースコードを見ました。

使い方としては、第1引数にブラウザからのパラメータ名を指定し、
第2引数には恐らく、BlobKeyの文字列を渡すのだと思っているのですが、
どうしてもテストがうまくいかない。
JUnitのトレースを見ると、以下のような表示になりました。
java.lang.ClassCastException: java.lang.String cannot be cast to java.util.List
at com.google.appengine.api.blobstore.BlobstoreServiceImpl.getUploads(BlobstoreServiceImpl.java:162)
at com.google.appengine.api.blobstore.BlobstoreServiceImpl.getUploadedBlobs(BlobstoreServiceImpl.java:135)
at slim3.demo.controller.blobstore.UploadController.run(UploadController.java:21)
at org.slim3.controller.Controller.runBare(Controller.java:111)
at org.slim3.controller.FrontController.processController(FrontController.java:491)
at org.slim3.controller.FrontController.doFilter(FrontController.java:277)
at org.slim3.controller.FrontController.doFilter(FrontController.java:237)
at org.slim3.controller.FrontController.doFilter(FrontController.java:199)
at org.slim3.tester.ControllerTester.start(ControllerTester.java:131)
at slim3.demo.controller.blobstore.UploadControllerTest.run(UploadControllerTest.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
どうやら、キャストで失敗しているようで、バグなのでは?という結論で、
読書会としてはタイムアップ。

その後、Twitterで、「slim3DemoがSDK1.6.0だと動作した」という報告が
あったので、私も同様にslim3Demoをダウンロードした後、実行してみると
同じ例外で失敗する結果となりました。

私の環境はSDK1.6.2.1なので、何か変わったのかと思ってSDKのソースをみると、
確かに変わっていました。

以下のリンクは、SDKのソースコードのDiffです。

元々は、Map<String, String>で扱っていたようなのですが、
Map<String, List<String>>に変わってしまったようで、
最新のSDKだとaddBlobKeyを使っても
テストは動作しないという結果になってしまいました。

現時点では、Slim3の更新を待つか、
requestScopeに以下のKeyで型をあわせてセットすれば動くかもしれません。
com.google.appengine.api.blobstore.upload.blobkeys

という事で、次回もtester(MockXXX)から読み進めます。

次回は3/6(火) 19:00〜を予定しています。ご興味がありましたらどうぞ。



2012年2月22日水曜日

Slim3 Source Code Reading #9

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

今回は読むのを忘れていたModelRefとAppEngineTestCase、AppEngineTesterの
辺りを読みました。

最初にModelRef.javaについて。
読み方は上から順番に読む感じでした。
まず、ModelRef.javaで気にしたのが、getModel()です。
例えば、Slim3本から引用すると、以下のような記述になります。
person.getAddressRef().getMode();
このgetModel()が、Memcacheから取得したインスタンスの場合
どうなるかが気になっていたからです。(課金的な面で。)

結果としては、Memcacheに入れてもModelRefに関してはデータストアへのオペレーション回数は減りません。以下のようにトランザクションがない、getを実行するからです。

model = Datastore.getWithoutTx(getModelMeta(), key);

ただし、これは初回だけで、2回目以降の呼び出しではメンバ変数を参照するため、getは実行されません。

また、getAddressRef()は、modelを更新する時にmodelMeta#modelToEntity()メソッド(これは自動生成される)を介してデータストアに保存されますが、データストアにはm.getAddressRef().getKey()で取れるKeyが格納されます。

次に、InverseModelRef.javaについて
これも上から順番に読み進めたのですが、ModelRef.javaと大体同じだったのですが、
getModel()が以下のようになっていました。

        model =
            Datastore.query(getModelMeta()).filter(
                mappedPropertyName,
                FilterOperator.EQUAL,
                key).asSingle();

これは、参照先のModelにプロパティを付加して、それを条件にQueryでデータを取得しています。ただ、1件ならKeyで取れば良いのに…という話になりましたが、よくわからなかったので、ひとまずスルー。

最後に、InverseModelListRef.javaについて
これも上から順番に読み進めました。このクラスは上記2つと違って、内部で、ModelQueryクラスのインスタンスを生成し、modelListを取得します。
複数のデータを取得するので、Filter、Sortも使えます。In-memoryのFilterとSortも使えます。

InverseModelRef、InverseModelListRefの両方とも、指定したプロパティ名のプロパティに格納されているKeyを取得するようになっています。

        public List getModelList() {
            Key key = getOwnerKey();
            if (key == null) {
                modelList = new ArrayList();
            } else {
                query.filter(mappedPropertyName, FilterOperator.EQUAL, key);
                if (!sortsSet) {
                    query.sort(defaultSorts);
                }
                modelList = query.asList();
            }
            return modelList;
        }

最後に、ControllerTestCaseか、AppEngineTestCaseをどちらを先に読むかを迷ったのですが、AppEngineTestCaseから読むことにしました。一般的に先にModelとServiceの作成、テストをするような気がしたので。

AppEngineTestCase.javaは、内部にAppEngineTesterのメンバ変数をもつ、
一般的なJUnitのメソッド(setUp、tearDown)があるだけでした。

したがって、AppEngineTesterを引き続き読み進めました。

AppEngineTesterは、色々ありました。

最初にstaticの初期化がありましたが、AppEngineUtil.isServer()の動きがよくわからなかったので、いろいろテストコードを書いて動かしつつ理解を深めました。
AppEngineUtil.isServer()については、SystemPropertyの"com.google.appengine.runtime.environment"がnullでなければtrueを返します。
これは、開発サーバだと"Development"、本番環境だと"Production"を返します。
(※詳細はドキュメント参照)
しかし、JUnitTestだとnullになります。(たしかに、JUnitTestはサーバではない。)
appengine-web.xmlに記述してみましたが、この値に関しては書き換えることができませんでした。(確認したのはJUnitTestでの実行のみです)
したがって、JUnitTestの時はサーバの環境が取り込まれていないので、初期化時に
必要なライブラリを取り込んでいます。
取り込んでいるのはApiProxyLocalImplとLocalDatastoreService、LocalServerEnvironmentのようです。

setUpでは、ApiProxyのEnvironmentとDelegateを退避しています。これは、恐らく、テストごとに初期化するためだと思われます。
メンバ変数のEnvironmentとDelegateはtearDownで元に戻されます。

tearDownでは、GlobalTransactionのロールバック、アクティブなトランザクションのロールバック、Memcacheのクリア、データストアに書き込みを行ったKeyのエンティティの削除、メールの削除が行われています。

ここで、最初に議論になったのが、「Commitしてしまうと削除されないんじゃないのか?」と
言う話をしました。パッと見た感じだと、deleteKeyを取得している部分がないからです。
が、結論を先に言っておくと、deleteKeyはDatastore#putを呼び出し、実行した時点で
登録されます。


これは、457行目の

ApiProxy.setDelegate(this);

で、自分のインスタンスをApiProxyのDelegateに登録しています。
これを行うことで、APIが実行されるたびに、Delegate<Environment>#makeSyncCall()が呼び出されるようになります。
Delegateはインターフェイスなので実装したクラスである必要がありますが、
AppEngineTesterはDelegateインターフェイスを実装しているので、
515行目のmakeSyncCall、599行目のmakeAsyncCallが呼び出される事になります。
あとは、中身を見ていくと、Service名で条件分岐してそれぞれのAPIに対する処理が
行われています。
Datastoreに関してはmakeSyncCall内でKeyを全て拾っているということになります。

それから、もう一つ重要だったのが、AppEngineTesterのenvironment変数です。
これは、ユーザの情報を持っていて、JUnitTestでUserServiceを利用する時に
重要です。
environment#setEmail()を使ってメールアドレスを設定しておくと、UserService.isUserLoggedIn()がTrueになり、認証済みの状態を作る事ができます。
注意する必要があるのは、emailを空にして、setAdmin(true)にすると、
”ログインしていないが、管理者権限がある”状態が作られてしまいますので、気をつけてください。
これを使うことで、Controllerのテストも可能になります。
同様にFederated Loginの情報も事前にセットしておけば、同じくテストすることが
できるでしょう。(※ただし、自分でセットすることになるので事前にFederated Loginした時の
環境は調査しておく必要があります)
他にもattributeにkeyと値をセットすれば同様に環境を作ることができます。

以下が、環境の動作確認で利用したテストコードです。
public class SampleTest extends AppEngineTestCase {
    @Test
    public void サーバチェック() {
        assertFalse(AppEngineUtil.isServer());
        assertNull(SystemProperty.environment.value());
    }
    
    @Test
    public void ユーザ認証テスト() {
        tester.environment.setEmail("test@example.com");
        tester.environment.setAdmin(true);
        
        UserService user = UserServiceFactory.getUserService();
        assertTrue(user.isUserLoggedIn());
        assertEquals("test@example.com", user.getCurrentUser().getEmail());
        assertTrue(user.isUserAdmin());
    }
}

他にも色々attributeにセットすると面白い事ができそうですが、
今日はここまでにしておきます。

次回は、2/28(火)です。続きでtesterを読み進める予定です。
https://sites.google.com/site/chugokugtug/sourcereading/slim3/10
ご興味がありましたらどうぞ。

2012年2月19日日曜日

Goの新しいコマンドとパッケージ管理

最近Go言語がVer.1に向けて開発されているようなのですが、
Weekly-2012-02-07辺りからコマンドが変わったみたいなので、
変更されたコマンドを少し使ってみようと思います。

まず最初に、環境変数$GOPATHを定義します。

この$GOPATH変数で指定されたパスに、ビルドしたパッケージのモジュールと、ソースコード、テストコードをひとまとめにして置いておきます。
ちなみに、GOPATHはPATHと同じように複数の場所を指定することも可能なようなので、
ソースコードをまとめる場所は複数作る事もできます。
ただし、go installコマンド(後で実行例を紹介しますが)は指定した最初のパスにモジュールを反映するようなので、注意が必要です。

さて、最初にHello Worldから。以下のソースをhello.goに保存しておきます。

package main

import (
 "fmt"
)

func main() {
 fmt.Printf("Hello World!\n")
}

上記のおなじみのコードを実行するには、以下のコマンドを実行するだけです。
以前は、8g hello.go && 8l -o hello hello.8 && helloみたいな感じでした。

$ go run hello.go

このコマンドを実行すると、コンパイル、リンク、実行まで一気にやってくれます。
多分、無事"Hello World!"が表示されると思います。
ちなみに、

$ go build -o hello hello.go

だと、hello.goと同じディレクトリに実行ファイルが生成されます。
go runコマンドだと実行ファイルは作成されませんでした。
(正確にはファイルが残っていなかった。どうしているんだろう?)

例えば、以下のようにパッケージを含むプログラムを作りたい時。

package main

import (
 "fmt"
 "mymath"
)

func main() {
 fmt.Printf("Hello World!\n")

 fmt.Printf("簡単な計算:1 + 1 = %d\n", mymath.Add(1, 1))
}

パッケージのファイルを$GOPATH変数で指定したパス配下のsrcディレクトリに
格納します。今回の例ではmymath.goというファイルで保存しています。

package mymath

func Add(a, b int) int {
 return a + b
}

保存した後、以下のコマンドを実行すると、パッケージがコンパイルされ、pkgディレクトリに生成されます。GOPATH変数にパスを複数指定している場合は、最初のディレクトリに格納されていると思います。

$ go install mymath

go installコマンドを実行した後、再度、go run hello.goで実行すれば、
簡単な計算もされることと思います。

しかも、Go言語標準のテストパッケージについても簡単に実行できるようになりました。
今回は、パッケージのテストを実行しています。
まず、mymath_test.goというファイルをmymath.goと同じディレクトリに作ります。

package mymath

import "testing"

func TestAdd(t *testing.T) {
 const ans = 2
 if Add(1, 1) != ans {
  t.Errorf("Add(%d, %d) == %d", 1, 1, Add(1, 1))
 }
}

テストパッケージは先頭がTestで始まり、最初が大文字(例:TestXxxみたいな感じ)の必要があるので、関数名はTestAddとしています。
あとは、同じパッケージのメソッドを呼び出し、結果を比較しています。
基本的にNGの条件を記述すれば良いです。
JUnitだと、正しい条件を記述するのですが、GoのTestingは反対なので注意してください。(反対というより、エラーを通知する関数しかないのか。)

プログラムを作成した後は、以下のコマンドで結果を確認します。

$ go test mymath

結果が正しければ、okとだけ出ます。

という感じで、パッケージのテストコードも実行しやすくなりました。

実行結果はこのBlogには載せていませんので、皆さんの環境で実行した結果をみて
検証してみてください。

ちなみに、GAE/GのSDKではまだこの仕組は導入されていませんのでGo言語の
ソースコードをダウンロードしてコンパイルするか、バイナリをダウンロードしてみてください。

注意点としては、私の書籍を購入してGAE/Gのプログラムを動かした方は
SDKのディレクトリにパスを通していると思います。これが、今回は邪魔になるので、
$PATHからSDKのパスを外してから動かしてみてください。

2012年2月15日水曜日

Slim3 Source Code Reading #8

Slim3 Source Code Reading #8を開催しました。

先に言っておくと…
今回はJSPを読むのみで終わってしまいました。Seasarを使っている人には「ほぼ常識」と言わんばかりの内容しかないと思いますが、よろしければ読んで頂ければと思います。

ちなみに、私に取っては新発見のものもあって、適用しようと思っています。

jspで記述する場合はf:h()など"f:"を付けて呼び出します。

h()
h()は引数がString型の場合は、エスケープして出力します。Keyの場合はKeyFactory.keyToString()の結果が出力されます。
String、Key以外の型の場合は引数.toString()が実行されます。
したがって、エスケープの必要がある場合は、必ずh()を使いましょう。

url()
引数に":"が含まれる場合はそのまま出力します。(恐らく、URLが書かれていると判断する)
ここでハマったのですが、中でrequest.getContextPath();を取得しています。
これはコンテキストパスを取得するのですが、GAEだとコンテキストルートの設定が
ないので、必ず空になるのでは?という疑問がでました。
実際、開発環境でTwitterサンプルを動かして、中身を見てみたのですが、やはり空でした。
必ず空になるなら無視しても良いような気もするのですが、疑問が残ったままとなりました。
処理としては、":"が付いていればそのまま返す(URLが書かれている)
引数が空の場合、
requestの中に"slim3.basePath"が含まれていれば、その値を、
含まれていなければ、サーブレットパスを取得し、
コンテキストパス+取得した値を連結して返す。

引数の先頭が"/"で始まっている場合、
コンテキストパス+引数を連結して返す。

空でもなく、先頭が"/"で始まらない場合、
コンテキストパス+"slim3.basePath" or サーブレットパス+引数の値を連結して返す。
返すURLはエンコードされたURLになる。

blobstoreUrl()

BlobstoreServiceからBlobStoreへのアップロードURLとリダイレクト先URLを設定した
URLを返す。リダイレクト先URLの生成ルールはurl()と同じ。
※このメソッドではコンテキストパスを取得していない。

nbsp()
空白を&nbsp;に置き換える。

br()
改行を<br />に置き換える。¥r¥n→¥n→¥rの優先順位で置き換える。
エスケープと改行、空白の変換を全て適用したい場合は、
f:br(f:nbsp(f:h(contents)))としないといけない。brとnbspが入れ替わるとおかしくなる。
f:hを最後にしても<br>がでてしまうのでNG。

locale():ロケールを取得
timeZone():タイムゾーンを取得

text()
引数で指定したnameに対する値をrequestから取得し、name="key" value="value"という文字列を出力する。valueの方は値がエスケープされる。inputタグなどで利用すると便利
※XXXArrayという名前だと例外が発生する。

hidden()
中身はtext()

checkbox()
checkboxで利用する。
引数で指定した名前がrequestに存在すればchecked属性が付加される。
※XXXArrayという名前だと例外が発生する。


multibox()
複数のcheckboxで利用する。value="value"が付加される。値はエスケープされる。
引数で指定した名前がrequestに存在し、引数で指定した値が存在すればchecked属性が付加される。requestのにはListを入れておかなければならない。Listでなければ例外が発生する。
※XXXArrayという名前でなければ例外が発生する。

radio()
radiobuttonで利用する。value="value"が付加される。値はエスケープされる。
引数で指定した名前がrequestに存在し、引数で指定した値が存在すればchecked属性が付加される。
※XXXArrayという名前だと例外が発生する。

select()
optionタグで利用する。value="value"が付加される。値はエスケープされる。
引数で指定した名前がrequestに存在し、引数で指定した値が存在すればselected属性が付加される。
※XXXArrayという名前だと例外が発生する。

multiselect()
複数選択時のoptionタグで利用する。value="value"が付加される。値はエスケープされる。
引数で指定した名前がrequestに存在し、引数で指定した値が存在すればselected属性が付加される。
※XXXArrayという名前でなければ例外が発生する。

errorClass()
引数で指定した名前のvalidationのエラーがあれば、
引数で指定したCSSクラス名を設定する。
エラーがなければ空を返す。

errors()
validationのエラーなどを出力する。検証エラー全てが表示される。

key()とhiddenkey()はDeprecatedとなっています。
それぞれ、h()かhidden()を使いましょう。

request()
protectedのメソッドなので直接利用する事はありませんが、
HttpServletRequestオブジェクトを取得しています。

後は、JavaScriptの話題になりましたが、私もまだまだJavaScriptのスキルが足りないようなので、微妙な感じで終了しました。

私のWebの技術が普通の人レベルになるにはまだまだ遠いようです。

次回のSlim3 Source Code Reading #9は2/21(火)です。
ModelRefとtesterを読み進める予定です。
ご興味がありましたらどうぞ。



2012年2月13日月曜日

第12回中国GTUG勉強会@岡山を開催しました。

第12回中国GTUG勉強会@岡山
https://sites.google.com/site/chugokugtug/event/12

を無事開催してきました。

今回は、発表者が初めての方にお願いをしたのですが、
皆さん、私の最初の時と違って、上手に時間配分も話もこなしていたと思います。

しかも、岡山開催では、一番多い人数の24人の方にご参加いただきまして、
大盛況だったと思います。

まだ、資料は公開されていませんが、そのうち資料が公開されたり、
Blog記事等が公開されると思いますので、当日の様子はそれをお待ちいただきたいと
思います。

今回はこういうイベントが重要だと思ったのが、Slim3 Source Code Readingなど
小さいイベントに対する意見が聞けたり、新たな発見が多いと思いました。

ちなみに、Slim3 Source Code Readingは最近開催時間が速いから参加できないという
指摘を受けました。
実は、小さいイベント(参加者も5名まで)なので場所も当日決定するスタイルを取っています。
その時の申し込みのメンバーを見て、岡山駅前なのか、もう少し南の方の場所なのか、
開催時間も早くしても良いのかを判断しています。
(例えば、2人の申し込みで、2人が揃っていれば待つ必要はないと思うので)

という感じでやっているので、参加してみようと思った方はお手数ですが、早めの
登録を行うか、私宛に連絡があれば、柔軟に対応するつもりです。

それと、「途中からだと参加しづらい」という事に対しては、一応、パッケージ毎で
区切って読み進めているので、途中からでも大丈夫だと思います。
(関連する所は、再度戻って読んでいたりします)

毎週やっているSlim3のソースコード読書会ですが、1周回ったら別のソースコードを読んでみるというのも良いかな?なんて思っていたりします。

読みたいソースコードはいろいろあって、Chromeとか、今日も触りましたが、BootstrapなどのHTML/CSSフレームワークとかですね。

あと、小さいイベントという事で、平日の夜の開催で2時間程度でハンズオンか、発表を小さくやってみるみたいなのもいいかなと思ったり。

基本、「オレオレ」勉強会のプランを考えているので、発表の講師は私が担当の予定です。(当然、内容も私がやりたい事をやる感じで)
ただ、ご意見等は取り入れようと思っています。

小さなイベントのプランを考えたきっかけは、岡山だとコミュニティが沢山できてきて、
週末(特に土曜日)のイベントの開催日の設定が難しいと感じているからです。
参加する人も毎週のように勉強会があると大変だと思いますし、
(懇親会で本気を出す方だと、毎週あると財布がブレイクすることになりますし…)
中国GTUGへの参加を回避するという選択に至る可能性もでてくるかもしれません。
(これに関しては、私自身がいろいろあってそういう状況に陥ってたりするわけですから文句は言えません。。)

もう、「重複覚悟」も視野にいれないとだめなのかもしれません。
(これは本当に切羽詰った時、(GTUGの場合だと3ヶ月の壁が迫ったとき等)に考える事になりますが…)

日曜日開催という意見もいただいたのですが、開催できても、前日に別の勉強会に
参加して次の日も!とはなりにくいかなと、個人的には思っています。
(私は独身ですから、プライベートな用事以外にも家の事もしなければいけませんし…)

という感じで、いろいろ考えてみたのをBlogに書き残してみました。
次回はまだ決めていませんが、思いついたら告知したいと思います。

2012年2月10日金曜日

CSS3でアニメーションさせる。

今日はPHPの授業で作れそうなものを探している途中に、
アニメーションを試してみました。
これは、Chromeだと動きますが、Firefox(10でも確認しましたが)だと動かないようです。

@keyframesの設定と、-webkit-animation-nameを指定することで、
スタイルを指定したElementがアニメーション
するようになります。

サンプルコードです。
sample.html
<!DOCTYPE html>
<html>
  <head>
 <meta charset="UTF-8" />
 <title>テキストアニメーション</title>
 <link rel="stylesheet" href="style.css" />
  </head>
<body>
  <h1>テキストアニメーション</h1>
  <div class="slide slide1">
 テキストアニメーション1
  </div>
  <div class="slide slide2">
 テキストアニメーション2
  </div>
</body>
</html>

style.css
.slide1 {
    position: relative;
    -webkit-animation-name: "slide";
    animation-name: "slide";

    -webkit-animation-duration: 1s;
    -moz-animation-duration: 1s;
    -o-animation-duration: 1s;
    animation-duration: 1s;
}

.slide2 {
    position: relative;
    -webkit-animation-name: "slide";
    animation-name: "slide";

    -webkit-animation-delay: 200ms;
    animation-delay: 200ms;

    -webkit-animation-duration: 1s;
    -moz-animation-duration: 1s;
    -o-animation-duration: 1s;
    animation-duration: 1s;
}

@-webkit-keyframes "slide" { 
    from {
        left: 200px;
    }
    to { 
        left: -200px;
    }
}

今回のポイントは、@-webkit-keyframesです。
この属性は、要素のアニメーションの動き方を指定します。
fromが、開始位置、toが終了位置となります。

slide1、slide2両方とも、animation-name:"slide"が指定されていて、このanimation-nameで指定した名前のkeyframesでアニメーションされます。

animation-durationはアニメーションが始まって、終わるまでの時間です。1sは1秒を
表しています。これが、500msになると、500ミリ秒になります。
動きを見ていると、animation-durationはまだ使えなくて、ベンダープレフィックスが
必要なようです。

-webkit-animation-delayは、アニメーションを開始するまでの待ち時間です。
時間の指定方法はanimation-durationと同じです。

これをChromeで動かすと、左から200pxから-200pxまで移動するアニメーションが
表示されます。端まで移動した後も止まらずアニメーションします。

おもしろいなと思ったのですが、授業では使えそうにないな…。
「Chromeでしか動かない」と言っても、IEやFirefoxで動かしたりするし…。

2012年2月7日火曜日

Slim3 Source Code Reading #7

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

今日の範囲はValidatorの辺りを読みました。
Validatorは入力チェックを行うためのクラスで、以下の型のチェックと、
範囲チェック、長さチェック、必須チェック、正規表現チェックができるようになっています。

  • byte(Validators#byteType())
  • short(Validators#shortType())
  • int(Validators#integerType())
  • long(Validators#longType())
  • float(Validators#floatType())
  • double(Validators#doubleType())
  • Number(Validators#numberType())
  • Date(Validators#dateType())
型チェックはそれぞれ、Byte.valueOf()等を呼び出し、例外がスローされるかどうかで
判定されます。ただし、NumberとDateはそれぞれ、DecimalFormatとSimpleDateFormatクラスでparseした結果、例外がスローされなければOKとなります。

実は、dateType()に関しては気を付けないといけないケースがあります。
以下のテストコードで確認したのですが、日付が不正なケースでもOKと判定される事があります。

    @Test
    public void 日付の確認() {
        input.put("arg1", "2012-02-07");
        input.put("arg2", "2012-02-30");
        input.put("arg3", "2012-02-1a");
        input.put("arg4", "2012-02-0a");
        input.put("arg5", "2012-02-01");
        input.put("arg6", "2012-02-29");
        
        Validators v = new Validators(input);
        v.add("arg1", v.dateType("yyyy-MM-dd"));
        
        assertTrue(v.validate());
        
        // 存在しない日付
        try {
            Validators v2 = new Validators(input);
            v2.add("arg2", v2.dateType("yyyy-MM-dd"));
            
            assertFalse(v2.validate());
        } catch(Exception e) {
            assertTrue(true);
        }
        
        // 日付が不正(2012-02-1aは正しいと判定される)
        try {
            Validators v3 = new Validators(input);
            v3.add("arg3", v3.dateType("yyyy-MM-dd"));
            
            assertTrue(v3.validate());
        } catch(Exception e) {
            assertTrue(true);
        }
        
        // 日付が不正(その2)(2012-02-0aは間違いと判定される)
        try {
            Validators v4 = new Validators(input);
            v4.add("arg4", v4.dateType("yyyy-MM-dd"));
            
            assertFalse(v4.validate());
        } catch(Exception e) {
            assertTrue(true);
        }
        
        // 日付チェック(正しくはこう)
        try {
            Validators v5 = new Validators(input);
            v5.add("arg3", v5.dateType("yyyy-MM-dd"), v5.regexp("^\\d{4}-\\d{2}-\\d{2}$"));
            
            assertFalse(v5.validate());
        } catch(Exception e) {
            assertTrue(true);
        }
        
        Validators v6 = new Validators(input);
        v6.add("arg5", v6.dateType("yyyy-MM-dd"), v6.regexp("^\\d{4}-\\d{2}-\\d{2}$"));
        
        assertTrue(v6.validate());

        Validators v7 = new Validators(input);
        v7.add("arg6", v7.dateType("yyyy-MM-dd"), v7.regexp("^\\d{4}-\\d{2}-\\d{2}$"));
        
        assertTrue(v7.validate());

    }

arg3の"2012-02-1a"という文字列はSimpleDateFormatで変換すると、日付は正しくありませんが、例外がスローされず、日付のインスタンスが返されます。
従って、本来は正しくない日付なのに、正しい日付という判定結果が返ることになります。
したがって、後半のチェックのように、正規表現でのフォーマットチェックも行う必要があります。
※これは両方必要です。正規表現のみだと、arg2のような、存在しない日付を通してしまうからです。

範囲チェックは、Validators#longRangeとValidators#doubleRangeがあります。(小さい範囲の型も同様にこれでチェックすることになると思います)

使い方は、v.longRange(3,10);と言う感じになります。
これだと、3以上、10以下(3と10を含む範囲)がOKとなり、それ以外の値の場合はNGとなります。doubleRangeに関しても使い方は同じです。

必須チェックはValidators#required()を指定する事でチェックがかかります。
nullか""(空文字)の場合、NGとなります。

長さチェックは、Validators#maxlength()、minlength()を利用します。
これは、文字数(バイト数ではない)をカウントして、引数で指定した文字数を超える(maxlength()の場合)か、下回る(minlength()の場合)かすると、NGとなります。
カウントの方法は、String#length()の値を比較しています。

Validationについてはこんな感じで、大した量ではありませんでしたが、ここから、使い方の話題に移りました。

これも今日見つけたのですが、ControllerクラスにasInteger()など、それぞれの型でrequestパラメータを取得する便利なメソッド達があります。
本来の使い方としては、Validatorでチェックをした後、asXXX()を使って、
変数に取り込む使い方が正しい使い方だと思います。
※nullを許容する場合はnullの扱いについても考慮は必要なので、プリミティブ型ではなく、クラスの変数に代入しましょう。
request.getAttribute("param").toString()をわざわざ変換したり、requestScope("param")をわざわざ変換するのは、カッコ悪いという事でした。
(※私のプログラムを見ると、混ざった状態でひどい事が判明したのでリファクタリングします)

asXXX()には、asKey()とasMap()もあります。asKey()はKeyFactory#keyToString()の結果をKeyに変換し、asMap()はHttpServletRequestをRequestMapに変換してくれます。

その後、少しjspのFunctionを見ていたのですが、そこで、blobstoreUrl()を見つけました。
前回の#6の時にBlobstoreのサンプルを見せたのですが、最初のBlobstoreに格納するURLを取得する部分がjsp側でf:blobstoreUrl(リダイレクト先URL)と書くだけで実現
できそうです。

それから、このBlogの記事を+1してくれていた、shin1ogawaさんのGoogle+のコメントがある事に気がついて(今日言われて気がついた)、FileServiceとか、Google Cloud Storageなんかの話題も触れる機会になりました。

それから、Blobstoreの最大ファイルサイズですが、前回32Mと言っていたのですが、
Goの場合(http://goo.gl/1lvU2)だけで、Python/Javaだと2GBまでいけそう(http://goo.gl/mX5Dy)です。

また、私が作ったGAEのアプリケーションで妙なリダイレクト(302のレスポンスを返す)が発生しているケースがあったので、聞いてみたのですが、
どうやら、リクエストのURLの最後の"/"が怪しいようでした。
ログを詳細に分析すると、IndexControllerを実行するURLで、最後に"/"があると、
200を返すのに、"/"がない場合は302を返していました。
IndexControllerでなければ、そういう現象は見受けられませんでした。

したがって、IndexControllerを使う場合は、URLの最後に必ず"/"を入れておかないと不具合の元になりそうな予感がしました。
特に、requestScope的な部分で。

次回は、jspをもう少し見るのと、ModelRef、testerと順番に読んでいく予定です。

2012年2月3日金曜日

Google App Engine Go SDK(Go1ベータ版搭載版)が公開されています

Go1betaが搭載されたGoogle App Engine SDKが公開されています。
http://code.google.com/p/appengine-go/downloads/list

先日公開されたGoogle App Engine SDK 1.6.2はr60.3という
ちょっと古めのバージョンのままでしたが、
もうすぐGo1がリリースされるのと、最新のGo言語と書き方も違うというのも
開発者からすると大変ですので、ここで、どのような違いがあるかを
まとめておきたいと思います。
(※恐らく、正式版でも大きな変更はないと思いますが、あくまでもGo1ベータでの
違いの確認です。また、ベースは私の書籍のプログラムをベースに見ていきます。)

なんとなくですが、近いうちに公式リリースもされそうな気がするので、
先にこのGo1betaで慣れておく方が良いかもしれません。

最新ドキュメントのURL
http://weekly.golang.org/

私の書籍「Go言語プログラミング入門 」でもリリース後は
読み替える必要がありますのでご注意下さい。

  • app.yamlのapi_versionは"3"ではなく、"go1beta"となります。
    • ※これに関しては、正式なリリースになれば変更されるでしょう。
  • httpパッケージ→"net/http"に変更
    • 通信関係のパッケージは大体netパッケージ以下に移動しています。したがって、これまでは、import "http"でしたが、import "net/http"となります。
  • os.Error→"error"に変更
    • Errorは、osパッケージで定義されたインターフェイスでしたが、パッケージ内のインターフェイスではなく、Go言語全体(?)で使えるインターフェイスになりました。また、エラーメッセージを取得するメソッド名がString()からError()に変わっています。
  • time.SecondsToLocalTime()がtime.Unix()に変更
    • これまで、ローカル時間を取得するメソッドはSecondsToLocalTime()でしたが、Unix()に変わっています。Unix()メソッドは、第1引数が秒までを指定し、第2引数でナノ秒(小さい時間)を指定するようになっています。
  • time.Seconds()がtime.Now()に変更
    • 現在日時を取得するメソッドが変更されています。Seconds()はメソッドになりました。秒を取得します。また、time構造体の中身が秒、ナノ秒、Location位置の3つで管理されるようになっています。
  • templateパッケージがtext/templateに変更
    • 使い方は変わっていませんが、templateパッケージが移動しているようです。
  • template.ParseFile()が、template.ParseFiles()に変更(2012/02/03追記)
    • template.ParseFile()でファイルを1つのみ解析していましたが、引数に複数のファイル名を渡す事で、複数のファイルを解析できるようになっています。これまで通り、1つでも実行できますので、メソッド名のみ変更しておけば問題ありません。
  • template.Must(template.New("html").ParseFiles("sample.html"))→template.Must(template.ParseFiles("sample.html"))にする。
    • template.ParseFiles()がtemplate構造体のメソッドと関数の2つに増えています。単純にhtmlを読み込んで実行するの(書籍の例など)であればtemplate.ParseFiles("sample.html")など、New()を呼び出す必要はありません。
  • datastore.Timeが削除されています。go1beta以降では、time.Timeを利用すれば良いようです。
    • フォーマットを指定したい場合は、Format()が用意されていますので、書籍では、fmt.Sprintfを使って書式指定していましたが、go1betaでは、comments_view[pos].Date = comment.Date.Format("2006/01/02 15:04:05")という感じになります。
    • 以前書いたBlog記事(Goでの日付変換)も参考にして下さい。
  • datastore.SecondsToLocalTime()も削除されています。
    • datastore.Timeで管理する必要がなくなりますので、time.Timeで定義し、time.Now()で取得して下さい。


2012年2月1日水曜日

Slim3 Source Code Reading #6

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

今日は、FileUploadの辺りを読む予定でしたが、
最近Slim3で開発を始めたので、それに対する疑問を聞いてみたり、BlobstoreAPIの
使い方を説明したりしました。

あとは、最近Google App EngineのアプリケーションをSlim3を使って
開発しているので、その時に疑問に思ったことを聞いてみたりしました。
先週と同じく、言いだしっぺの2人だったので、すぐ脱線してしまいました。

ちゃんと、FileUploadの部分も読みました。

が、HTTPのリクエストに対する知識がないと、意味不明な部分もあったりして、
Chromeのデベロッパーツールでhttp headerの中身をみたりしました。
個人的には十分な内容だったと思います。

では、一つ目のBlobstoreAPIを利用したファイルのアップロードについては、
以下の手順でファイルをBlobstoreに格納します。

  1. アプリケーションのファイル選択画面を表示する。
    1. ここで、POST先のURLはBlobstoreService#createUploadURL(url)を呼び出して、BlobstoreAPIで利用するURLを指定する。
    2. 引数のURLはBlobstoreにデータを格納後にリクエストされるURLを指定する。
  2. アップロード後に呼び出されるURLで、BlobstoreService#getUploadsを呼び出し、Blobstoreに格納したデータのBlobkeyを取得する。(複数ファイルに対応しているため、戻り値がMap<String, List<BlobKey>>の型になっている。)
    1. この時取得したBlobKeyは忘れてはいけない。(BlobKeyで格納しているデータにアクセスするため、BlobKeyを参照できないとデータを参照する事も、削除することもできなくなる。※ただし、admin consoleからなら削除可能)
  3. BlobKeyを引数にBlobstoreService#serve()を呼び出すことで、HttpResponseにBlobstoreに格納されているデータを書きこんでくれる。
    1. 画像であれば、<img>タグのsrcに指定することで、画像が表示される。
    2. 動画であれば、<video>タグのsrc
    3. 音声であれば、<audio>タグのsrc
  4. 同じく、Blobkeyを引数にBlobstoreService#delete()を呼び出すことで、Blobstoreのデータを削除する。ただし、Entityに格納している場合は、Entityの中身(BlobKey)を読みだした後に削除しないと、BlobKeyが参照できないのでdeleteできなくなる。
ちなみに、私のGoの本を参考にサンプルプログラムを作りましたが、(私だけかもしれませんが)意外と作れます。(Go→Javaができたという事は、その逆もまた可能なはず)

BlobstoreAPIはファイルサイズが最大32Mバイトまでとなっていますので、
大きなデータは格納できません。

脱線した話として、

  1. 自作のControllerに親クラスを作ったりするかどうか。
    1. これはやる。Sessionなど、全体に渡って取得する場合はやる。
  2. データストアから読みだした後、表示用に編集した値を保持するクラスをどこに作るか。
    1. 特にルールはないので、どこでもいいんじゃないか。
    2. Modelで編集用のデータを返すメソッドを定義する
  3. Keyのnameに名前を格納しておくと結構いい感じ。
    1. エンティティを読み出すのとKeyのみ読み出すのとでは、コスト的に7分の1
  4. とりあえず、良く使うデータはmemcacheに突っ込んどけば良いんじゃないかな?
    1. 更新頻度の問題がある。
      1. インスタンスが停止する場合。→所詮その程度なので安く収まるのでは?
      2. 結局は運用してみないとわからない。
  5. 課金設定にしたら、9ドル/月
    1. 最大9ドル/日と勘違いしていた。(1日9ドルだと月2万以上…)
    2. 無料枠を使い切る→9ドルの枠でがんばる→枠を越えたら…。
  6. Javaで作ったアプリケーションの最初の起動は遅い…
    1. Javaは初回のアクセス(インスタンスを起動する)時は一呼吸あるけど、Goだとそれはない。
最後にFileUploadの話題

  1. FrontControllerで、Content-Typeを確認して、"multipart/"で始まるかどうかをチェック。
    1. "multipart/"の場合、ファイルがアップロードされてきた判定。
      1. この場合、MultipartRequestHandlerのインスタンスが生成される。
    2. その他は、普通の(?)リクエスト
  2. MultipartRequestHandler#handleで、FileUploadクラスのインスタンスを生成する。
    1. FileUploadクラスで、ファイルのサイズの制限設定を読み込む。
      1. 現時点のblankプロジェクトの設定はコメントアウトされているので初期値が使われる。
      2. 初期値は-1なので、「制限なし」
    2. この中で、いろいろな処理をして、FileItemクラスのインスタンスを生成する。
      1. MultipartRequestHandlerはかなり頑張ってます。
      2. ちなみに、ファイルを読み込む時のバッファは8 * 1024(8192)バイトです。ファイルが大きいと実行されるステップ数も増えます。
    3. 最終的にControllerでFileItem item = requestScope("formFile")の1行でファイルが取得できる。
  3. Serviceでアップロードされたデータを格納する処理を実行すれば、Datastoreにファイルが格納される。
個人的に思うこと。

  1. BlobstoreAPIを利用したほうが良い。
  2. ただし、BlobstoreAPIはファイルの中身をチェックできない
    1. UploadはAPI用のリンクへのリクエスト
    2. downloadは直接HttpResponseに書きこまれる
  3. したがって、中身をチェックしたい場合は、Slim3のFileUploadを使うほうが良い。
  4. あと、課金状況で判断するのも必要かもしれない。
  5. 32Mバイトを超えるファイルを格納する可能性があれば、BlobstoreAPIは使えないのでSlim3のFileUpload一択。(※ただし、そんなに大きなファイルを格納しようと思うと、30秒の時間制限を考える必要がある)

次回の#7は2/7(火)19:00〜の予定です。
少人数で行っているので制限が厳しいですが、ご参加をお待ちしております。

2012年1月29日日曜日

JQueryの初歩

最近、「コアjQuery+プラグイン/jQuery UI開発技術実践技法」という本を
読んでいるのですが、

この本です。


jQueryの基本的な部分をまとめておこうと思います。

使い方としては、jQueryのソースコードをダウンロードしてきて、
サーバのディレクトリに置いておき、それをロードして使えば良いのですが、
Googleが提供しているJavaScriptAPIを利用すれば、指定したバージョンの
jQueryのソースコードをGoogleからダウンロードして使う事ができます。

例えば、以下のようになります。

<script src="http://www.google.com/jsapi">
</script>
<script>
 google.load("jquery", "1.7");
 google.load("jqueryui", "1.8");
</script>
<script>
 $(function() {
  $("#datepicker").datepicker({
   dateFormat: 'yy/mm/dd'
  });
 });
</script>

これは、HTMLのidにdatepickerと指定されている要素を取得し、
そこに日付入力ダイアログを表示し、入力後、要素に反映するというコードです。

headタグの所に、上記のコードを入れておくと、ブラウザにページが表示された時には
日付入力ダイアログが表示されるようになっています。

まずは、jQueryを使う上でよくわからないのが、$()です。
これは、「要素の配列を返す関数」だそうです。引数に特定の文字列を指定すると、
ページの中に配置されている要素から条件に一致する要素の配列を返してきます。

例えば、
$("div.header")
と、指定すると、ページの中にあるdiv要素でclass="header"と指定されている要素が
取り出されます。したがって、
$("div.header").hide()
とすると、divタグのclass="header"の要素が非表示になります。

次に、
$("div#header")
と指定すると、ページの中にあるdiv要素でid="header"と指定されている要素が
取り出されます。したがって、
$("div#header").hide()
とすると、divタグのid="header"と指定されている要素が非表示になります。

最後に、
$("div a")
と指定すると、divタグの中に含まれるaタグの要素が取得されます。
$("div a").style.backgroundColor="#FF0000"
とすると、divタグの中に含まれるリンクの背景が赤くなります。

という事で、まとめると、

  • $()関数の中に指定する文字列はページ内の要素を取得する条件を指定する。
  • $()関数は条件に一致する全ての要素を取得する。
  • "."で区切ると、classの条件、"#"で区切るとidの条件、" "(空白)で区切ると要素の中に含まれる要素が取得される。
  • "."、"#"、" "は混在(複数の条件を指定)して使うことができる。基本AND条件となる。


これを利用すると、マウスの動きに連動して背景を変えるなんてことも簡単にできるようになります。

他にも、$()関数の引数に関数を渡すと、「ドキュメント・レディ」ハンドラと
言って、JavaScriptでいうと、window.onloadで実行する処理に相当するものだそうです。
したがって、headタグの中のscriptタグ内で、
$(function () {
    $("input#name").hide();
});

なんて処理を書いておくと、inputタグのid="name"の要素を初期状態を非表示にできます。

便利なので、しっかり使ってみたいと思います。

2012年1月25日水曜日

Slim3 Source Code Reading #5

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

今回も@sinmetalさんと2人での開催となりました。
内容は、

  1. 私の仕事でGoogle App Engineを使ったアプリケーションを作ることになったのでその質問
  2. Memcache.javaを読み進める。
  3. UserServiceを使った認証についての雑談
  4. org.slim3.datastoreパッケージのCreationXX.javaについて

でした。

1.は単純に仕事で使う事になった(した)ので、アプリケーションを開発している話を少ししました。

2.が本題のMemcacheの機能です。
方向性として、Memcacheの存在確認、MemcacheへのGet、Putを中心に読みました。
(今、deleteも確認しましたが、deleteもラッパーでした)
読み進めた所、Memcacheの存在確認はcontains(key)、Memcacheへの登録はput()を
それぞれ呼び出しますが、中身は、MemcacheServiceのメソッドを呼び出している
だけでした。

しかし、Getだけはそのままではなく、MemcacheDelegate.javaの214行目から
始まる、getInternal()でいろいろ処理されています。
MemcacheServicePb.MemcacheGetRequestクラスのインスタンスと、
MemcacheServicePb.MemcacheResponseクラスのインスタンスを生成し、
233行目のmakeSyncCall()を呼び出します。

このmakeSyncCall()は中で、ApiProxy.makeSyncCall()を呼び出し、
(恐らくSDKよりも下の)APIを呼び出し、Memcacheに登録しています。
MemcacheはKey-Value形式で、しかも、Valueはbyteデータを格納する事に
なっていますので変換処理が必要ですが、オブジェクトをbyteデータに変換する処理も
このあたりで行われているようです。

なぜ、このようにGetだけ、ただのラッパーではなかったのかは、分かりませんでした。
(恐らく、AppEngineのAPIを実行すると処理が遅いとか、無駄な待ち時間がかかるなど
あるのだろうと判断して終わらせました)

その次は、Memcacheの次に読み進めるのをどれにするか選んでいた時に、
ユーザ認証の話になり、私が、UserServiceのすばらしさを一方的に話した感じでした。

利点として、

  1. Googleの認証機構に全てお任せできる。
  2. 認証済みであれば、ユーザ情報が取れるので、利用できる。(Mail APIによる通知や、ユーザの識別、データの管理、アクセス管理など)
  3. データストアに(多分)SessionIDなどを登録する必要がなくなる。


1つ目は自力で認証処理を作る手間が省ける。かつ、(多分)自分よりも質の良いセキュリティが確保できる。しかも、Gmailが見れる状態(すでにログイン済み)だと、
アプリケーションの認証の処理が不要な場合もでてくる(入力の手間が減る)。
2つ目は自力認証の必要が無いので、ユーザ情報の有無で表示の制御ができる。何らかの処理の完了通知などにも利用できる。限られたユーザのみ見せるという機能も簡単に作れる。しかも、認証情報をデータストアに残す必要もないので、データストアのオペレーションの削減に繋がる。
3つ目はSlim3だとSession情報をデータストアに格納している(?)ということなので、
cronなどで、データストアの不要な情報を削除する処理を実行しなければならないが
その手間が省ける。データストアのオペレーションにも影響する。

ということが考えられます。

最後に4つ目のCreationXX.javaについてですが、org.slim3.datastoreパッケージに
CreationUser.java
CreationDate.java
CreationEmail.java
ModificationUser.java
ModificationDate.java
ModificationEmail.java

というファイルが存在しています。
このファイルはAttributeListenerインターフェイスを実装しているのですが、
何のためにあるのかよくわからないファイルでした。

が、実は(ドキュメントにも書かれているのですが)、アノテーションを設定すると、
ModelMetaで、データストアにPutする前に、現在の日付や、ユーザ情報を自動的に
取ってきて、プロパティに設定してくれます。
(Serviceで設定する必要がありません)

    @Attribute(listener = CreationUser.class)
    private User user;

上記のようにすることで、勝手に登録されます。
CreationXXとModificationXXの違いは、
CreationXXはすでに設定済みかどうかを判定し、設定されていない場合のみ登録されます。
ModificationXXの場合は、内容にかかわらず、値を上書きします。

次回は1/31(火) 19:00〜で、util系を読み進める予定です。

2012年1月22日日曜日

本格アプリを作ろう!Androidプログラミングレシピを読みました。



本格アプリを作ろう!Androidプログラミングレシピを読みました。
この本はインプレスジャパンから発行されている書籍です。

内容は、Android2.3(Gingerbread)で動作するソースコードを元に
記述されています。翻訳担当の方が動作確認とAndroid4.0での動作についても
注釈として記述されているため、最新のAndroidに対するサポートもされています。

概要は以下の内容です。

  1. Androidを始めよう
  2. ユーザインターフェイスのレシピ
  3. 通信ネットワークのレシピ
  4. 端末ハードウェアやメディアと連携するレシピ
  5. データ永続化のレシピ
  6. Androidシステムと連携するレシピ
  7. ライブラリを活用するレシピ
  8. 付録
    1. スクリプト環境の構築
    2. Android NDKで性能を高める
    3. アプリ設計のガイドライン
本書は、基本的な所から、大体のデバイスの使い方、アプリケーションの連携が
説明されています。
文章量は多く感じられますが、詳細に説明されているため、じっくり読んでみる方が
良いと思います。また、レシピ形式で細かく分けられているため、ゆっくり進める事も
可能です。ソースコードもほぼ掲載されているので、写経も可能です。

1点気になったのはリソースの解放処理が省略されている事です。
Android2.3だと不要なのか、本来の機能の説明のために省略したのか
分かりませんが、画像とか、ネットワークリソースの解放の記述が無かったのが
気になりました。

この書籍は今回は献本して頂きましたが、この書籍は購入しても損はしないと
思いました。

Google+に投稿したコメント
https://plus.google.com/u/0/114183076079015753160/posts/ULQoezrSWN4
https://plus.google.com/u/0/114183076079015753160/posts/UPiADfBJ1TG

オープンセミナー2012@広島で発表してきました。

オープンセミナー2012@広島で講師として発表してきました。

今回はQueryとTransactionについてお話ししました。
資料とソースコードは公開してありますので、興味がありましたらごらんください。

資料
https://docs.google.com/present/edit?id=0ASEzu8hKOt7rZHJwbjl2NV83MWRreDV6dmMy

デモ用ソースコード
https://github.com/tyokoyama/osh2012demo

今回はQueryとTransactionなのでそんなに話題として
少ないかな?と思ったのですが、
よく考えると、Queryを語るにはIndexの前提も必要で、
Transactionの話をしようとすると、エンティティグループの前提も
必要だったので、当日追加してみたのですが、早口でお話ししないと
いけないぐらいの内容になってしまいました。
今思えばTransactionは外せば良かったかもしれません。
しかも、Go言語と言いながらほとんどGoogle App Engineの仕組みの話になってしまったような。

Queryについても文字列のInequalityFilterに関しての動作も
今回のデモ用のアプリケーションだと良い例ではなかったのかもしれません。
住所録の方が良かったかも。前方一致の検索結果になるのを見せるには
そちらの方が説明しやすい結果もでたかもしれないし。

他の講師の方の話も聞きたかったのですが、資料の調整と
すぐに反省モードに入ってしまったので、あまり聞けていません。

その後、懇親会で、宇品港(広島港)の前の公園の中にある、テントでかきを食べる
かき小屋で懇親会をしました。食べ物は精算所で先に買って、(追加する時も精算所に行く)飲み物は自販機で買うスタイルで、テント小屋の中で炭焼きで海鮮、肉類を焼いて食べました。

その後、@Toro_kunの家に行って、たこ焼きを初めて焼きました。そして、食べました。
深夜まで色々な真面目な話をして、満足して帰りました。
ありがとうございました。

2時から@soudai1025さんを車で送り届けて、朝5時に無事家に付きました。

次は第12回中国GTUG勉強会に出席の予定です。

2012年1月18日水曜日

Slim3 Source Code Reading #4

今日はSlim3 Source Code Reading #4を開催しました。
参加者が私と@sinmetalさんの2人だったので、当初想定していたメンバーになりました。

今回は、

  1. EntityGroupとQueryについての前提知識の共有(というよりは、前回間違った説明があったのでその辺りの共有)
  2. Transaction、GlobalTransactionのソースコードを読み進めました。
  3. EntityGroupに関する実装上の話題。構成とか。Ancestorクエリにするケースなど。
  4. sinmetalさんの「サービス単体でテストコードを動かすと、Cross Group Transactionが正常に動作するのに、プロジェクト全体のテストを行うとテストに失敗する件」

を行いました。

Transactionについては、Datastore.javaにあるDatastore#beginTransaction()を読み進めました。
ただ、この中身は、Google App EngineのTransactionAPIを利用しているだけで、Slim3で特別何かをしているということはありませんでした。
ただし、TransactionのOptionでSystem.getProperty("slim3.useXGTX")の結果がTrueである場合、Cross Group Transactionが有効になります。
System.getProperty("slim3.useXGTX")はappengine-web.xmlに記述してある値を取得します。※現時点ではデフォルトでは記述がないので、値はfalseです。

GlobalTransactionに関しては、かなり大変な事をしてあるので、概要だけ説明を書いておきます。
(sinmetalさんが検証コードを書いて試してくれるという事なので、その結果を待ちます)
実際、GlobalTransactionが必要になる事はこれからは少なくなってくると思いますので
(現時点で既にDeprecated)Cross Group Transactionを利用した方が良いと思います。

具体的にはどうしているかと言うと、

  1. トランザクションを開始する。Datastore.beginGlobalTransaction();
  2. エンティティをGet or Putする時、Lock用のキーMapに、現在のキーの祖先が存在しているかを確認する。存在していれば、例外をスローする。ただし、存在していても、キーのTimestamp+30秒を超えていれば無視する。
  3. GlobalTransaction#Get or Putを呼び出す。その時、対象のエンティティの祖先のキーを取得し、Lock用のMapに退避しておく。キーにはTimestampを保存しておく。また、Getの場合はKeyのみ、Putの場合はKeyとEntityのペアをJournal用のMapにPutする。
  4. トランザクションをCommit or Rollback(GlobalTransaction#commit、rollback)する。この時、GlobalTransactionの場合は、Journal用のMapからGet用のKeyを削除し、Put用のEntityをslim3.GlobalTransactionカインドにPutする。Get用のKeyにはEntityが登録されていないので、Entity==nullであれば、Get用のKeyと判定する。ただし、単一EntityGroupのみの場合(Lock用のMapが空)はLocalTransactionと判断しそのままCommitする。
  5. GlobalTransactionの場合、TaskQueueにキューを登録する。登録するキューはGlobalTransactionServletへのリクエストとして登録する。
  6. GlobalTransactionServletで、RollForwardトランザクションの場合、Commitされた場合は、GlobalTransaction用のカインドにある、KeyとEntityのペアを取り出し、本来のカインドのエンティティにPutする。Rollbackトランザクションの場合は、カインドから対象のエンティティを削除する。

大体の処理は上記のようになっています。
正確な処理については、大変ですが、ソースコードを読んでみてください。

最後にsinmetalさんから相談された、「単体テストでHRDのテストが動作しない件」ですが、見せていただいたテストコードには、setUp()でSystem.setProperty("slim3.useXGTX", "true");の
記述がありましたが、この設定を反映するAsyncDatastoreDelegateが何度もインスタンスが生成されるようであれば良いのですが、メンバとして保持している、
Datastoreクラスがstaticであるため、実行開始後、1回しか設定を読み込まないようになっているようです。したがって、System.setProperty()が呼び出される前にDatastoreクラスのメソッドが呼び出されてしまうとその時点でデフォルトのトランザクションオプションが反映されてしまって、System.setProperty()が意味をなさなくなってしまいます。

これについても検証コードの結果を待ちましょう。

ただ、この後にも話をしたのですが、開発環境HRDのオプションの遅延は、
かなり大きいので、Queryのテストをしたい時にまでHRDの遅延が発生すると
テストが通らない問題に直面してしまいます。
一つは、遅延確率を設定するオプションを0にして遅延しなくするという方法を
提案しましたが、テストクラスの本数が多くなってくると面倒という問題があるようです。
また、Thread.sleep(2000)とか入れてみるという提案もしたのですが、
これも同様に本数が増えてくると、テストの実行時間が長くなってしまうので
全体のテストを行う時に問題になる。ということでした。

今日は、時間も長めにやりましたが、内容も濃かったです。
疲れました。

2012年1月14日土曜日

Go言語プログラミング入門の正誤表を更新しました。

Go言語プログラミング入門正誤表に追記しました。
http://takashi-yokoyama.blogspot.com/2011/12/go.html

appengine/datastoreパッケージのPropertyLoadSaverについて、
誤記があったので、詳細に説明をしておきたいと思います。

PropertyLoadSaverインターフェイスのドキュメントです。
http://code.google.com/intl/ja/appengine/docs/go/datastore/reference.html

書籍では、「データを登録した状態でないとプログラムが応答を返さなくなる」という
事が書かれているのですが、これは、サンプルコードを作成している時の試行錯誤の
途中段階の誤認識をした状態で記述をしていました。

実は、PropertyLoadSaverインターフェイスのLoadメソッド、Saveメソッドの定義は
以下の通りです。(書籍のサンプルコードから抜粋したものです)
func (g *Guest) Load(c <-chan datastore.Property) os.Error {
func (g *Guest) Save(c chan<- datastore.Property) os.Error {
どちらもchannelが引数になっています。このchannelについての説明はP.63付近に
少し記述をしていますが、CriticalSectionと同じで、自分のスレッドをブロックします。
ブロックが解除されるのはchannelに値がセットされた時か、close(c)された時と
なります。LoadStruct()、SaveStruct()はメソッド内でchannelの処理を行っているため、呼び出せば構造体に値がセットされ、channelの処理も適切に行ってくれます。

書籍のプログラムだと、Saveメソッドの方では、何もしていないため、channelのブロックが解除されないことになり、現時点ではプログラムが応答を返さなくなってしまいます。

したがって、Load()の方でも、同様に「何もすることがない」からといって何もしないと
プログラムが応答を返さなくなってしまいます。

2012年1月12日木曜日

AppEngine Office Hourに参加した。

Japanese AppEngine Office Hourに参加したので、そこでの質問と回答をメモ
https://plus.google.com/u/0/110554344789668969711/posts/d3ykURrCnDt

Python2.5以下でないとssl警告が出る件
→Goのappcfg.pyはpython2.5が指定されているので、
$ python ../appcfg.py update app
など、python新しいバージョンのPythonを明示的に指定すると、警告はでない。
または、python2.5の方にsslモジュールを適用する。

ユーザ名を取得する場合。
初回ログイン時に表示名を入力するなどしてもらって、表示名を入手する。
ATNDとかどうだったかな…?
→多分、同じ動作をさせていると思われる。
→確認したらユーザ設定画面があって、そこでユーザ名を登録していた。

Emailの先頭部分(@以前)を表示
→だと、メールアドレスがバレてしまうので…。
上記の対応を行うしか無いだろう。

Java、PythonもNicknameを取得すると、メールアドレスの先頭部分が取れるかも。

Goを使うメリット
・ネイティブであるので、立ち上がりが一番速い。メモリの効率も良い。

XGTransactionの最大エンティティグループについて(仕様で最大5グループまで)
http://code.google.com/intl/en/appengine/docs/go/datastore/overview.html#Cross_Group_Transactions

開発環境でHRDのオプションを入れるコマンド
$ python ../dev_appserver.py --high_replication ./app


2012年1月11日水曜日

Support Packageを使ってFragmentを利用する

先日、私が公開している「サポセン前の問診票」アプリケーションにFragmentを適用した
形に作り替えたので、その時に発生した現象と、Framgentの使い方をメモしておきます。

前提:
※Support Package v4を利用する。

以下が大体の手順です。

  1. レイアウトXMLにFragmentを含むレイアウトを定義する。
  2. FragmentActivityを継承したActivityのクラスを作成する。
  3. 普通に起動。


だけです。終わり。というと内容が薄いので、

レイアウトは以下のような感じになります。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <FrameLayout
        android:id="@+id/framelayoutmain"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
     <fragment
         android:id="@+id/fragment1"
         android:name="com.sample.ui.MainFragment"
         android:layout_width="fill_parent" android:layout_height="fill_parent" />

    </FrameLayout>
</LinearLayout>

Activityのソースコードはこれまで通りとなります。

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Activityが実行された時点でFragmentも呼び出されて表示されますので、
Fragment外で処理する事が無ければ、Activity側では何も記述しなくても良いです。
ポイントとしてはFragmentActivityを継承しないと実行時にエラーになってしまいます。

あとは、Fragmentのプログラムの方で処理するプログラムを
実装すれば良いです。

次に、FragmentをBackStackに積む方法は以下のコードです。

SubFragment fragment = new SubFragment();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.framelayoutmain, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(null);
ft.commit();

FragmentTransactionクラスがFragmentのBackStackの状態を管理しているようです。
ft.replaceでレイアウトに配置されているFragmentを置き換えます。
これは、Fragmentが複数配置されている場合でも、それぞれのFragmentが配置されているViewを指定して置き換える事ができます。
setTransitionは置き換え時のアニメーションを指定します。
addToBackStackは置き換え後の状態(アクティビティ全体)をBackStackに積みます。
最後にcommitを実行して確定します。

注意点として、ft.replaceした場合、下にあるFragmentの内容が見えてしまいます。
(背景が透明になる)
したがって、背景を画像や色を指定して、下のFragmentが見えないようにしないと
いけません。(これはSupport Packageのみの現象?)
しかも、ListFragmentを利用した時(他のFragmentでも?)、画面をタッチすると、下に配置されたFragmentもタッチを検出しますので、処理が実行されてしまいます。
BackStackに積む前に下のFragmentの処理が実行されないようにEnabled = falseにするか、
getFragmentManager().getBackStackEntryCount()をチェックして、積まれていたら
処理をスキップするなど工夫をしないといけないようです。

Google+に書き残したコメントです。

https://plus.google.com/u/0/114183076079015753160/posts/cyodhHmgRy9
https://plus.google.com/u/0/114183076079015753160/posts/3kDyzNe1mej
https://plus.google.com/u/0/114183076079015753160/posts/UHcTUgp7Hky