2010年9月 1日

アジャイルな開発をチームでやってみた(2010年版) - その2
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

テストコード書いてますか? HIROKIです。

murahashiに続いて、テストファーストを導入してみての振り返りをします。 まず、どうやってチームにテストファーストのスタイルを持ち込んだのか。

1.テストが重要だという共通認識を持つ。

前のプロジェクトではテストコードは、ほとんどありませんでした。 その中で、開発になれていない人や新たに人が投入され、 極少数ですが、デグレーションが起きました。

その経験を元にテストが重要だという共通認識を持つことができました。

2.プロジェクト開始時からテストファーストに踏み切る気持ちが必要

テストコードを書かなければコミットさせない。ぐらいの気持ちが必要です。 実際に何度も、本格的な実装が始まる前から口にしていました。 「うちのチームはテストを書かなければコミットを許さない。」と。

3.でも、テストコード書いたことないよ?

テストコードを書いたことのないメンバーもいるかと思います。 そんな人は下記の参考書を写経することから始めましょう。

symfonyを使うことが前提だったので、上記の本をlimeで置き換えて写経しました。

また、下記のような動画もとても参考になります。

4.そんなこと言っても、自主的に学習する人ばかりでないよ?

初日にペアプロしちゃいましょう。

そのチームでTDDに一番詳しい人と、TDD初心者な人をペアにして、 実装を始める初日かその前(できるだけはやく)にペアプロをしましょう。

ペアプロは1モジュール書き上げるくらいが良いかと思います。 そのチームで利用する開発環境の基本的なテストコードの書き方として後に参考にします。

私のチームではsymfony+memcached+lime+hudsonという環境でしたので、 どのタイミングでfixtureを流してmemcachedをクリアして、 どういったテストの書き方をしていくのか参考になるものを1つ作り上げました。 (細部に関してはチームみんなの意見をあつめて、検討してつくりました。)

これができれば他のモジュールにも適応して開発していくだけです。

5.じゃあ、やってみた経験から為になること教えてよ?

symfonyでテストファーストを実施してみた経験から具体例をあげてみます。

fixtureを理想的なものを最初につくっておく

テーブルの定義だけではなくて、 テーブルの中身のサンプルを全部のテーブル用意しておくこと。 データ自体は簡単なデータでよい。

モジュールをつくる度にあらたなfixtureをつくることを避けられる。 fixtureをつくるのが結構めんどくさい。 テストを書きたいのにデータがないというフラストレーションがたまる。 結局、つくらないといけないけど、他のモジュールで使っているfixtureと整合性がとれているのか?

規模が大きくなればなるほど、面倒なことになります。

limeのOKメソッドは使うな

bool値をチェックするメソッドとしてokメソッドがLimeには用意されていますが、 これを使用することを非推奨としました。

たとえば、テストが失敗したケースを考えると。

$test->ok(false);

実行画面

not ok 21
#     Failed test (./hogeTest.php at line 34)

という感じで返ってきます。ですが、isメソッドで書けば

$test->is(false,true,trueが返ってくるはず);
not ok 22 - trueが返ってくるはず
#     Failed test (./hogeTest.phpat line 35)
#            got: false
#       expected: true

という感じで出力が返ってくる。コメントもあるので、わかりやすい。

okメソッドだけだと情報が少なくて苦労するケースがあるので、面倒でもisメソッドで書くことにしました。

コメントではなくて、コードに含めろ

非推奨

// trueが返ってくるはず
$test->is($foo->bar(),true);
not ok 22
#     Failed test (./hogeTest.phpat line 35)
#            got: false
#       expected: true

推奨

$test->is($foo->bar(),true,trueが返ってくるはず);
not ok 22 - trueが返ってくるはず
#     Failed test (./hogeTest.phpat line 35)
#            got: false
#       expected: true

コメントを元にテストの場所や書いたテストの意図がわかるので、メソッドに含めましょう。

まとめ

build_status.png

このグラフがhudsonが1回目のビルドから529回目のビルドまでのテストにかかった時間と成功・失敗の結果です。

チーム全体で常にすべてのテストをパスしている状態を保つという意識とそれに伴う行動の結果です。

Files=59, Tests=3152

現時点で59ファイル、3152のテストをパスしています。

赤色の部分も目立ちますが、テストが成長していることが目で見てわかります。 こうやって、テストに支えられていることも再認識することも大切なことなのかな。とも思います。

今回は、チームでテストファーストをやるためのステップと、少し具体的な例を紹介してみました。

2010年8月26日

アジャイルな開発をチームでやってみた(2010年版)
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは murahashi です。
アジャイルな開発をチームでやってみている(2010年)のですが、いざやってみると結構ハマリどころがありました。やってみたことを共有しておこうと思います。

かたちから入ろう

acts_as_agile_armor_600_457.jpg
朝会
アジャイルな開発と言えば朝会なので、朝会から始めました。
開始時刻をメンバーで決めて、それぞれが昨日やったこと、今日やること、おしらせ、困っていること、を共有しました。
さらに、朝会前に社内wikiにメモ書き程度の項目を書いておきます。これにあらかじめ目を通すことで、一番の課題に時間を集中することができました。

acts_as_agile_anti_pattern_50_67.jpg
アンチパターン・決めた時刻を守らない
11時から朝会始めようと決めたのに、11時過ぎに汗だくで飛び込んできて「遅れてすみません」「wiki書いてません」「wiki読んでません」というのは、チームの空気を悪くするだけでなく、単純に全員の時間を無駄にしてしまいます。
時刻を守るか、守れるルールに変えましょう。ごめんなさい。

できることからやろう
組み込めそうなものからとりいれてみました。
具体的には、『達人プログラマー』で言う三本柱(テスト・自動化・早期デプロイ)、朝会、継続的な統合(CI)、テスト駆動開発(TDD)、テスト用データベースの独立、分散VCSを使う、などです。
CI, git, TDDは一気に導入しました。テスト用データベースの独立は必要になってから取り入れました。

acts_as_agile_anti_pattern_50_67.jpg
アンチパターン・あれもこれも
一個一個見ればそれはあったほうがいいんだろうなと思いますが、きりがありません。多すぎて身構えてしまうし、開発が乗ってくるまでにどれだけかかるんだよと。
かといって完全マスターしないと増やさないポリシーも意味がありません。要はバランスです。
今思えばgitの導入がかなり説明不足でした。その時はsubversionをフル活用した社内ライブラリとの接合をとるのでいっぱいいっぱいでした。

チームでやろう
今までもタスクが個人レベルまで分解されたあとは、アジャイルというかXPというか"ぼくのかんがえたTDD"でやってみてました。これが結構できるようになったなーと思ってチームでやるようになったのですが、ひとりxpとチームxpのあいだの一歩は思ったより大きかったです。
逆説的ですが、ひとりxpが完璧にできるようになるのを待つ必要はないと思います。最終的にチームでやらない意味がわからないので、できることからさっさとチームで始めるのが良いと思います。

acts_as_agile_anti_pattern_50_67.jpg
アンチパターン・チームで決めた方針は変えない
合わなかったら、そして合理的な理由があればさっさと方針を変えましょう。
はじめのルールのままテストがどんどん遅くなっていっているのでどうにかしたいです。

テスト駆動で開発しよう
テストコードを先に書く事で、テスト可能な設計になります。むしろテスト可能な設計でしか書けません。テストコードが存在することで、テストコードの範囲内で正しく動くっぽいことが確認できます。テストコードがなければ、正しく動くっぽいことの確認すらできません。
また、テストを壊してしまったら、CIサーバのhudsonが容赦なく "Build faild in Hudson" なるメールをガッシュガッシュ投げつけてくるので、想定の範囲内でデグレードを防ぐことができます。

acts_as_agile_anti_pattern_50_67.jpg
アンチパターン・テストを書かなければ失敗しない
テストを書かなければ失敗しないのですが、それでは意味がありません。また、通らなくなったテストをコメントアウトしたり、消したりしてテストを通してはいけません。
さらに「いけません」とチームに言ってるのに「他の作業するところで影響でるから」と実装だけ修正したりすると、「なんであいつだけ」となるので以後気をつけます。
ただし"たまに落ちるテスト"はどうしていいかいまだによくわかりません。

イディオムに従う
symfonyのイディオムはjobeetなので、jobeetに従っておくと共通認識をつくりやすいです。php, symfony, lime, jobeetのイディオムから外れるときは明確に意図を持って外れるようにしましょう。
「正しいunit testでは」みたいな話ばかりをしていても仕方がないので。しましたけど。

そんなこんなやってみた構成

  • php5.3, symfony1.4, mysql5.1, memcache, flashlite1.1
  • テスティングフレームワーク lime
  • BTS trac
  • VCS git
  • CI hudson
  • デプロイ capistrano
だいたいそんなかんじ

やってみてのハマりどころ

アジャイルな開発のスイートスポットは新規開発?
不慣れなチームだったので別にスイートスポットではありませんでした。
ケータイ向けアプリのflash
flashが重要な役割を占めるけど、limeでテスト駆動開発...そこは割り切って開発することにしました。
デプロイするcapistranoのレシピは早々に書いて動かしてたのですが、flashとphpが出来上がってきたところでがっちゃんこになりました。そして、がっちゃんこしたら結局わりきった接合面でなんでか動きません。TDDでmodelを厚く作ったのに...
だいたい原因は、symfony にも不慣れな人ばかりだったので、そんなに厚くないcontroller部分の値の受け渡し部分なことが多かったです。
ケータイ向けopensocialに特有の部分
テスト用擬似ブラウザsfBrowserのclickとかredirectとか(NDAかもしれないので省略されました)

acts_as_agile_stone_wall_600_569.JPG
fixtureの硬直化がDBのschemaの硬直化を呼ぶ
fixtureをyamlで書いてそれを毎回DBにロードしてるので、スキーマ変更するたびにyamlの編集が必要になりました。テスト書き換えるだけでもしんどいとおもうひとばかりなのに、yamlをえんえん編集してるのはもっとひどくてかなりダメージを受けてました。赤→緑→リファクタリングのサイクルで脳汁が出る人ばかりではありません。自然と、 DBのschemaが硬直してしまいました。
factory_girlはやくきてくれー。
全体的に
やりたいやりたい言ってる私がコーチになるどころかそんなに役に立ててないのが割と原因の一つです。でも私もわかんないよ。手探り手探りです。

あわせてよみたい

これから

問題にぶつかったなかで、今一番多い解決策は"とりあえずわりきって前に進む"であることがおおいので、もうちょっとましな解決tipsのエントリを挙げられるといいなあと思います。
以上デース

2010年8月25日

リビドーに赴くままlibmemcachedをPHPから使ってみる
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

Keita です。

浴衣姿の女性は人類の宝だと思います。

さて、phpのpeclのmemcache'd'ってご存知でしょうか。 長い歴史をもつpecl memcache(名前近過ぎだ)はその拡張の中でmemcachedプロトコルの実装をしていますが、pecl memcachedはlibmemcahcedにその実装を依存を任せています。

利用するときの違いとしては
memcache:

  • インストール時に他のライブラリに依存しない
  • 長いこと使われてるライブラリなので安定性はある
memcached
  • memcacheよりも利用できる機能が多い
  • 今後もプロトコルの拡張があったときにlibmemcachedに追従するだけなので対応が早いことが予測される
という点が挙げられます。(まだまだ一杯ありますが・・)

memcacheよりも利用できる機能が多いというのはかなり大きく、具体的に言うと、set時に比較して他のプロセスが更新されてないかどうかを確認してから更新するという処理をアトミックにできるMemcached::casは、揮発性の高い本家のmemcached(デーモンの方)はともかく、最近の永続的なストレージとして、memcachedプロトコル互換をうたうKVSを使う時には是非とも使いたい機能だと思います。

追記:
kamipoさんより、はてブのご指摘でPECL::memcacheも3.0.0(alpha)より、casをサポートしているというご指摘いただきました。
よくよく見てみると確かにCasに限らずappendやバイナリプロトコルのサポートなどいままでほしかった機能が実装されています。(どんだけやる気なんだと作者に対して強く尊敬の念をいだきました)安定性やなどかんがみると、PECL::memcacheは非常に有力な候補となると個人的には思いますのでこれは別途調査してみたいとおもいますし皆様も選択の際はぜひ頭の隅にいれていただければ幸いです。

前置きが長くなりましたが、libmemcachedとpecl memcachedの構築に本来は簡単なのになんだかとっても、手間取ったので記録を残したいと思います。

前提としては、PHP5.3.3の環境でRPMを作成する環境は整っているという前提です。 とりあえずrpmmacrosをの設定
cd ~/
echo "%_topdir `pwd`/rpmbuild" > ~/.rpmmacros
mkdir -p ~/rpmbuild/{SRPMS,RPMS/x86_64,SOURCES,SPECS,BUILD}
libmemcachedのrpmを作成
tar zxvf libmemcached-0.39.tar.gz 
cd libmemcached-0.39
./configure
make rpm
インストール
$ cd ~/rpmbuild/RPMS/x86_64/
$ rpm -ivh libmemcached-devel-0.39-1.x86_64.rpm libmemcached-0.39-1.x86_64.rpm 
これで、limemcachedがインストールされました。 そのあとpeclのRPMを作るのですが、会社のRPMだとラボブログに書くちょっと癖があったりするので、ここでは、Les RPM de Remi - PackagesからSRPMを拝借することにします。 libmemcachedも含めてここにRPMが公開されてるのでそちらをつかってさくっと入れてしまうのが楽だとは思いますが、RPM作るのに手間取ったのでその手順を書くというのが本記事の主旨なのでとりあえずこのまま進めます。
# PHPはすでに入っていて、phpizeが使える前提で・・・
$ wget http://rpms.famillecollet.com/SRPMS/php-pecl-memcached-1.0.2-1.remi.src.rpm
$ rpm -i php-pecl-memcached-1.0.2-1.remi.src.rpm
$ cd ~/rpmbuild/SPECS
$ rpmbuild -ba php-pecl-memcached.spec
$ cd ~/rpmbuild/RPMS/x86_64/
$ rpm -ivh php-pecl-memcached-1.0.2-1.x86_64.rpm
ちなみに何を手間取ったかというと、libmemcached-0.39で、pecl-memcached-1.0.1を動かそうとしていたからずーっと動かなかったりしていたのですが。 Change Logにしっかり書いてありました。 それに気がついた時いろいろ雄たけびあげました。 何はともあれ、これで安心してCasが使えます。動作としてはこんな感じのイメージです。
<?php
$memcached = new memcached();
$memcached->addServer('localhost', 11211);

$key = 'hoge';
$value1 = 'fuga';
$value2 = 'mote';

$memcached->set($key, $value1);
$cas = null;
$resultValue = $memcached->get($key, null, $cas);
assert($resultValue === $value1); //fugaが返される

$success = $memcached->cas($cas, $key, $value2);
$resultValue = $memcached->get($key, null, $cas);

assert($success === true); //更新は成功
assert($resultValue === $value2); //更新されmoteが返される


$memcached->set($key, $value1);
$resultValue = $memcached->get($key, null, $cas);

$memcached->set($key, $value1); // 取得したあとに誰かがもう一度セットしたとする

//更新しようとする
$success = $memcached->cas($cas, $key, $value2);

assert($success === false); //更新失敗
assert($resultValue === $value1); //更新されずhogeが返される
そんなわけで、どうでもいいですが、僕は、秋祭り、一緒に浴衣でいってくれる女子を募集中です。 記事ともどもご参考になれば幸いです。

2010年8月19日

線画のゴミ除去方法
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

初めまして。ウノウでデザイナーをしているyamamotoです。

最近は色々と便利な本が出ているので、当たり前の技術になっていますが
知っているとチョビット便利なフォトショップ上でのゴミの取り方をご紹介します。

画像データはフォトショップ上にて直接描いてしまったサンプルですが
実際は紙に描いたものをスキャニングしたものだと仮定して下さい。

スキャニングし線を抽出するとどうしてもゴミがついてきてしまいます。
そこで便利なゴミの取り方のご紹介です。


1:線画部分だけを抽出した状態

ラボブログ001.jpg


レイヤー部分をダブルクリックしレイヤースタイルを表示します
※フォトショップ上レイヤータブからもレイヤースタイルを選択できます。

この時、境界線を選択し、塗りつぶしのタイプをカラーにし
カラーをわかりやすい色にします。ここでは赤を選択しています。

描画モードを通常にし位置を外側にします。
※大抵はカラー以外はデフォルト設定で大丈夫だと思います。

自分の見やすいサイズを選択しOKを押します。

2:レイヤースタイルを表示した状態

ラボブログ002.jpg

上記の行程を行うと線画に赤い線がつきます
また、キャラクター以外のヌキの部分のゴミにも同様に
赤い線がつくのでわかりやすいと思います。

後は消しゴムツールでゴミの部分を消してあげると奇麗になりますが
取りきれない部分もありますのである程度は自分で確認する必要もあります。


3:境界線が選ばれている状態

ラボブログ003.jpg


ラボブログ004.jpg

この後は設定したレイヤースタイルをそのままドラッグして
ゴミ箱に捨てれば完成です。







3:線画完成!

らぼぶろ.jpg

あとはリビドーのままに塗ってください!
おつかれさまでした!





PNG画像書き出しの「あとちょっと軽くできたら...」を可能にするファイル作成TIPS
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは。ウノウでデザイナーをしているChristelです。

銀色の全身スーツを着、透明チューブの中を走る車で移動する21世紀で暮らす私たち。
夢のクラウド化が囁かれる昨今ですが、未だ携帯電話関連の画像作成には、スカイツリーよりも高い容量の壁があります。
「Flashの3階層メニューで文字も画像で100kb以内でよろしく」とか
「あれとこれとそのファイル合わせて一回で表示するのは20kb以内でね」など、
関係者の皆さんは、1バイトに泣き、1バイトに笑う日々を送っておられることでしょう。

今日はそんな皆さんに
「ビットマップデータを透過PNGで書き出すとき、クオリティを下げずにサイズをちょっとだけ減らせる画像作成のコツ」
をご紹介します。

ニッチ過ぎて申し訳ないです。


1)まずPhotoshopで画像を作成

miyakami_1.jpg
↑ 一世を風靡した感のあるWEB2.0風。画像の痛さには目を瞑ってください。


2)そのまま背景透過のPNG24で書き出します

miyakami_2.jpg
↑背景透過、マットなし、のPNG24です

15.3kbのPNG画像ができました
miyakami_3.jpg

...と、ここまではフツー。


3)それではここでFireworksを立ち上げましょう

miyakami_4.jpg
私たちの未来を明るく照らしだすFireworks先生。ゴールドカラー(きいろとも言う)がまばゆい。


4)先ほどのPSDファイルを読み込み、そのまま背景透過のPNG32で書き出します

miyakami_5.jpg
背景透過、マットなし、のPNG32です


5)ではいま作成したファイルサイズを見てみましょう

miyakami_6.jpg
14.7kbのPNGファイルができました


...なんということでしょう~!!

Photoshop書き出し15.3kb
Fireworks書き出し14.7kb

Photoshopで作成したファイルより軽い♪

二つのファイルを比べてみてもほとんど遜色ありません。

miyakami_7.jpg

ありがとう、ありがとうFireworks先生!!


...端的に申し上げると「Fireworksを使え」という事ですが、意外と皆さんご存知ないようです。
大量のPNGを使用するFlashや携帯サイト等では差が歴然です。
小さな差異を笑ってはいけません。
また、GIFの場合もFireworksを使うと若干軽くなる場合が多いです。



★☆ ↓↓おまけ↓↓ ☆★

◆◆試してみよう~...その1◆◆

ちなみにFireworksによるJpeg画像の書き出しではどうでしょうか。
同じ写真をPhotoshopとFireworksで同じ設定で書き出してみました。


miyakami_8.jpg
元の写真。みかんゼリーおいしい。


画質80、最適化チェック、マットなしのJpeg書き出しです
miyakami_9.jpg

プロパティを見てみると

左:Photoshop書き出し19.2kb
右:Fireworks書き出し10.6kb


Fireworks書き出しの方が俄然軽いですが、やはり画像が荒くなっています。
しかしながら目を覆うほどの劣化ではないので、臨機応変に使い分けて頂ければ良いでしょう。



◆◆試してみよう~...その2◆◆

では更に同じ写真をPNGで書き出したらどうでしょうか。

Photoshop 背景透過、マットなし、のPNG24
Fireworks 背景透過、マットなし、のPNG32
で書き出し。

miyakami_10.jpg

左:Photoshop書き出しのファイル
右:Fireworks書き出しのファイル


とってもきれいでどちらも遜色ありません。

さてファイルのサイズはというと... 

Photoshop書き出し75.2kb
Fireworks書き出し84.2kb

と非常に残念な結果に...。


書き出しは用途を考えて正しく行いましょう~!


では、素敵な毎日を。。。



2010年8月13日

MySQLのチューニングのためのデータの集め方
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

いつの間にか会社で古株になったyamaokaです。

webアプリケーションのバックエンドにMySQLを使っている場合、 クエリ(SQL)のチューニングをする必要がありますよね。 皆さんはチューニングの計画をどのように立てていますか。

もちろん、既に明らかに重いことが想定されているページがあれば、 その処理で使われているクエリを中心にEXPLAINなどを使って解析していけばいいと思います。

でもそうではなく、全体的にクエリの見直しやチューニングを行いたい場合は 実際に実行されているクエリを確認していくという作業が必要です。 そこで使うことができる3つの方法について書きたいと思います。

遅いクエリを記録する

MySQLにはスロークエリログといって、 実行に時間がかかったクエリを記録する機能が最初から付いています。 /etc/my.cnfに次のように設定を書けば実行時間が1秒を超えたクエリが出力されるようになります。

slow_query_log = 1
slow_query_log_file = /path/to/mysql-slow.log
long_query_time = 1
オンラインでset globalを使って変更する場合は次のようにします。
set global slow_query_log = 1;
set global slow_query_log_file = '/path/to/mysql-slow.log';
set global long_query_time = 1;

出力されたログファイルをMySQLに付属しているmysqldumpslowというツールで解析すると便利です。次のように実行すれば、平均の実行時間が長い順にソートして表示してくれます。

mysqldumpslow -s at /path/to/mysql-slow.log
クエリのパラメータは数値はN、文字列はSに置換して表示してくれるので 同じクエリをまとめてチェックすることができます。

実行回数の多いクエリを記録する

実行時間は短いけれど多くの回数実行されるクエリというのもあります。 これらは通常のスローログには出てこないのですが、 実は負荷の大部分を占めている、ということもありえます。 キャッシュなど別の方法を考えることで アプリケーションの負荷を減らすことができるかもしれません。

従来MySQLではlong_query_timeに1秒以上の値しか設定できませんでしたが、 MySQL 5.1から1秒未満の値も設定できるようになりました。 つまり、次のように/etc/my.cnfで「0」を設定すれば 全てのクエリが記録できることになります。

slow_query_log = 1
slow_query_log_file = /path/to/mysql-slow.log
long_query_time = 0

もちろん、全てのクエリの記録は負荷も大きくかかることを 理解してから設定を行うようにしてください。 また、ログの容量も大きくなるので、ディスクの空き容量にも注意が必要です。 オンラインでset globalを使って一時的に値を0に変更し、すぐに元の値(1など)に戻すのがオススメです。

set global slow_query_log = 1;
set global slow_query_log_file = '/path/to/mysql-slow.log';
set global long_query_time = 0;
# 後で set global long_query_time = 1; で元に戻す

出力されたログファイルをmysqldumpslowで次のように解析することで、 実行回数の多い順に表示することが可能です。

mysqldumpslow -s c /path/to/mysql-slow.log

mysqldumpslowは他にもいろいろ機能を持っているので、「--help」を付けて実行して 一度オプションを確認してみるといいかもしれません。

追記: MySQLでは/etc/my.cnfに次のような設定をすることで全てのクエリを記録することが可能です。 最初から全ての記録を保存したい場合はこちらの方法もいいかもしれません。

log = /path/to/mysql-query.log

インデックスを使っていないクエリを記録する

多くの場合、インデックスを使用しないクエリは遅いです。 インデックスの設計は計画的に行う必要があると思いますが、 今現在インデックスを使用していないクエリはどれなのか知りたい場合があると思います。 次のように/etc/my.cnfに記述することでインデックスを使用していないクエリを スロークエリログに記録することができるようになります。

slow_query_log = 1
slow_query_log_file = /path/to/mysql-slow.log
long_query_time = 5
log_queries_not_using_indexes = 1
オンラインでset globalを使って設定する場合は次のようにします。
set global slow_query_log = 1;
set global slow_query_log_file = '/path/to/mysql-slow.log';
set global long_query_time = 5;
set global log_queries_not_using_indexes = 1;

出力されたログファイルは今までと同じように mysqldumpslowを使って解析していくことになると思います。

終わりに

最近は、開発するときにフレームワークに付属のORマッパーを使ったりして 実際に発行されるクエリを意識しないことが多くなっていると思います。 もちろんそれはメリットだと思うのですが、実際に実行されるのはクエリ(SQL)である以上、 完全に意識しないで済むということはありません。

実際に発行されているクエリを眺めつつ、 少しでもパフォーマンスのよいwebアプリケーションを作っていけたらいいと思います。

追記: オプションの変数名がMySQL 5.1基準のものになっていなかったのでMySQL 5.1をベースに修正しました。

2010年8月12日

JavaライブラリでOAuth認証
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

みなさん、こんにちは。 7月にウノウに入社しました細川です。

私は、これまでいろいろなオープンソースの恩恵にあずかってきましたが、こちらから貢献をしたことは、ほとんどありませんでした。この記事がお役に立てれば幸いです。

ウノウ入社前にはJavaを主に使っていましたので、今回は、JavaのOAuthライブラリを使う方法について書いてみたいと思います。

OAuthとは

OAuthは、セキュアな認証手段として多く使われてきています。 twitterもweb APIの認証手段として採用しています。 OAuthを使った認証を行うことで、ユーザーはアカウントやパスワードを知られることなく、第三者サービスにAPIの使用を許可することができます。また、その認証は第三者サービスに関係なく取り消すことができます。

OAuth Community http://oauth.net/

OAuthコミュニティが各種プラットフォーム向けのライブラリを公開していますので、今回は、Javaのライブラリをビルドして使用します。

Code - OAuth http://oauth.net/code/

Repository http://oauth.googlecode.com/svn/code/

OAuth Library をビルドする

Eclipseを使ったビルドを紹介します。

新規プロジェクトを生成し、ショートカットメニューから「インポート」を選択します

import_project_from_svn.JPG

SVNからプロジェクトを選択」を選択し、次へ

check_out_from_svn.JPG
ロケーションにhttp://oauth.googlecode.com/svn/code/ を指定します。

select_check_out_folder.JPG
フォルダ「java」を選択します。

select_check_out_option.JPG
チェックアウトオプションは上画像のように選択してください。

チェックアウトできましたら、次にプロジェクトの設定を行います。
build_path_source.JPGのサムネール画像
Default output folderに「oauth/bin」を設定します。
また、Sourceタブから上画像のようにフォルダをパスに設定します。

build_path_libraries.JPG
LibrariesタブからSVNからチェックアウトしたlibフォルダ以下にあるライブラリをビルドパスに追加します。
これで、ライブラリがビルドされるはずです。

このままですと、classファイルがばらばらになっている状態ですので、Fat Jar Eclipse Plug-Inを使って、Jarファイルにアーカイブしましょう。インストールはこちらから。

Fat-Jarは、実行形式のJar ファイルを簡単に作れたり、参照する外部JarファイルライブラリもJar内に配置できたりする優れものですが、今回は、単純なアーカイブを作成します。

Fat-Jarをインストールしたら、プロジェクト上でショートカットメニューを開き、「Build Fat Jar」を選択します。

configure_fat_jar.JPG
Jarアーカイブの名前を設定して「Finish」で作成されます。

OAuthライブラリを使う
今回は、Twitterを例にとって、サーブレットから認証、APIアクセスを行うこととします。

Twitterのアカウントをもっている方なら、Twitterアプリケーション からアプリケーションを登録することができますので、実際に試したい場合には登録してください。
登録が完了すると、アプリケーションがOAuth認証に必要とする2つのキーと認証のためにアクセスする3つのURLが手に入ります。

これらの提供された値とOAuthライブラリの主に4つのクラスを用いて認証を行い、Twitter APIにアクセスしてみましょう。

4つのクラス
net.oauth.client.OAuthClient
通信を行うクラス
実際に使用されるHTTP通信の実装をラップします。

net.oauth.OAuthServiceProvider
プロバイダを定義しているクラス
プロバイダから提供されるURLをラップします。

net.oauth.OAuthConsumer
コンシューマ(第三者サービス)を定義しているクラス
コンシューマ・キー、コンシューマ・シークレット、コールバックURLなど、コンシューマに結びつく値を扱います。

net.oauth.OAuthAccessor
アクセスを定義しているクラス
アクセス・トークン、リクエスト・トークン、トークン・シークレットなど、個々の認証に関わる値を扱います。

認証URLを生成する
OAuth認証において認証はTwitter(プロバイダ)が行い、その結果がリダイレクトによりサイト(コンシューマ)に通知されます。
ですから、まず、認証先へのURLを生成し、それをユーザーにクリックしてもらうか、リダイレクトして認証先に移動させる必要があります。以下に、リダイレクトにより認証先に飛ばすサーブレットのdoGetメソッドを示します。
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	
	String destUrl = request.getParameter("dest");

	OAuthClient client = new OAuthClient( new URLConnectionClient());
	OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
	OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
			CONSUMER_KEY, COMSUMER_SECRET, provider);
	OAuthAccessor accessor = new OAuthAccessor( consumer);
		
	String redirectTo = null;
	try{
		try {
 			//get request token first from Twitter.com
 			HashMap params = new HashMap();
			params.put( "oauth_callback", 
					OAuth.addParameters(accessor.consumer.callbackURL,
							"dest", destUrl));
			//get request token first from Twitter.com
			client.getRequestToken(accessor, null, params.entrySet());
		} catch (OAuthException e) {
			throw new OperationFailedException( "It failed to authenticate Twitter account", e);
		} catch (URISyntaxException e) {
			throw new OperationFailedException( "It failed to authenticate Twitter account", e);
		}
			
		//build redirect path to twitter authentication page
		redirectTo = OAuth.addParameters(
				accessor.consumer.serviceProvider.userAuthorizationURL,//auth URL(twitter.com)
				"oauth_token", accessor.requestToken//
				);
	} catch (IOException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}

	response.sendRedirect( redirectTo);
}
最初の部分ですが、ここでは、OAuthClientをもっとも単純なURLConnectionクラスを使うように初期化しています。
OAuthClient client = new OAuthClient( new URLConnectionClient());
Jakarta Commons HttpClientのv3やv4を使用することもできます。その場合にはそれぞれnet.oauth.client.HttpClient3、net.oauth.client.HttpClient4クラスを使って初期化します。しかし、Google App Engine for Java環境では、スレッドが使えない関係で、URLConnectionClientしか動作しません。 

次に、OAuthライブラリの各クラスをTwitter(プロバイダ)から提供されたパラメータを使って初期化しています。
	OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
	OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
			CONSUMER_KEY, COMSUMER_SECRET, provider);
	OAuthAccessor accessor = new OAuthAccessor( consumer);
アプリケーション登録で手に入れたパラメータを使って各クラスを初期化しています。

次のパートでは、パラメータを積んでTwitter(プロバイダ)と通信を行い、リクエストトークンを受け取ります。
 			//get request token first from Twitter.com
 			HashMap params = new HashMap();
			params.put( "oauth_callback", 
					OAuth.addParameters(accessor.consumer.callbackURL,
							"dest", destUrl));
			//get request token first from Twitter.com
			client.getRequestToken(accessor, null, params.entrySet());
リクエストトークンは認証をリクエストする際に必要になります。 
パラメータ"oauth_callback"は、リダイレクトによるコールバックが呼び出すURLを指定します。アプリケーション登録でコールバックURLは登録しているのですが、ここではそのURLにパラメータ"dest"を追加するために使っています。"oauth_callback"を指定しない場合、アプリケーション登録で登録したURLにリダイレクトされます。

 次のパートでは、getRequestTokenでTwitter(プロバイダ)から取得されたリクエストトークンを取り出し、認証先URLを生成しています。
		//build redirect path to twitter authentication page
		redirectTo = OAuth.addParameters(
				accessor.consumer.serviceProvider.userAuthorizationURL,//auth URL(twitter.com)
				"oauth_token", accessor.requestToken//
				);
うまくいけば、ユーザーは以下のようなTwitterのページに誘導されます。Twitterと連携するWebアプリケーションを使っている方なら、見たことがあるのではないでしょうか。
twitter_auth_confirmstion.JPG
ユーザーが「許可する」もしくは「拒否する」をクリックすると、"oauth_callback"で指定したURLにリダイレクトされます。
次にコールバック先での処理を見てみましょう。

コールバックでの処理
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	
	String destUrl = request.getParameter( "dest");
	
	String requestToken = request.getParameter(OAuth.OAUTH_TOKEN);
	if( requestToken == null){
		//rejected by USER
		response.sendRedirect( destUrl);
		return;
	}
	
	String verifire = request.getParameter(OAuth.OAUTH_VERIFIER);
	if( verifire == null){
		//rejected by USER
		response.sendRedirect( destUrl);
		return;
	}
	
	OAuthClient client = new OAuthClient( new URLConnectionClient());
	OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
	OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
			CONSUMER_KEY, COMSUMER_SECRET, provider);
	OAuthAccessor accessor = new OAuthAccessor( consumer);
	
	accessor.requestToken = requestToken;
	
	try {
		HashMap params = new HashMap();
		params.put( OAuth.OAUTH_VERIFIER, verifire);
		//get access token and secret from twitter.com
		client.getAccessToken(accessor, null, params.entrySet());
	} catch (OAuthException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	} catch (URISyntaxException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}
	
	
	//Retrieve user's information
	OAuthMessage oMessage = null;
	try {
		oMessage = accessor.newRequestMessage("GET", "http://api.twitter.com/1/account/verify_credentials.json", null);
	} catch (OAuthException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	} catch (URISyntaxException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}

	OAuthResponseMessage rMessage = client.access(oMessage, ParameterStyle.AUTHORIZATION_HEADER);
	int status = rMessage.getHttpResponse().getStatusCode();
	if( status == HttpResponseMessage.STATUS_OK){
		String jsonStr = rMessage.readBodyAsString();
		JSONObject jObj = JSONObject.fromObject(jsonStr);
		String userId = jObj.optString("id");
		accessor.setProperty( "id", userId);
	}else{
		throw new RuntimeException( "It failed to authenticate Twitter account STATUS CODE:"+status);
	}
	
	//Store access token and secret to Cookie
	TwitterAPIUtil.storeTokenToCookie(accessor, response);
	
	response.sendRedirect( destUrl);
}  	
まず、認証によりTwitter(プロバイダ)から付け加えられたパラメータを取得しています。
	String requestToken = request.getParameter(OAuth.OAUTH_TOKEN);
	if( requestToken == null){
		//rejected by USER
		response.sendRedirect( destUrl);
		return;
	}
	
	String verifire = request.getParameter(OAuth.OAUTH_VERIFIER);
	if( verifire == null){
		//rejected by USER
		response.sendRedirect( destUrl);
		return;
	}
これらがセットされていなかった場合、ユーザーが認証を拒否したと考えられますので、あらかじめコールバックURLに追加したパラメータに積んでおいた飛び先URLにリダイレクトさせます。

先の処理と同様にOAuthのクラスを初期化後、認証により手に入ったリクエストトークン、ベリファイアを使ってTwitter(プロバイダ)からアクセストークンとトークンシークレットを取得します。
	accessor.requestToken = requestToken;
	
	try {
		HashMap params = new HashMap();
		params.put( OAuth.OAUTH_VERIFIER, verifire);
		//get access token and secret from twitter.com
		client.getAccessToken(accessor, null, params.entrySet());
	} catch (OAuthException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	} catch (URISyntaxException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}
アクセストークンとトークンシークレットはAPIをコールするために必要になります。 うまく取得できた場合、すでにOAuthAccessorオブジェクトにアクセストークンとトークンシークレットは格納されていますので、Twitter APIを呼び出すことができます。
	//Retrieve user's information
	OAuthMessage oMessage = null;
	try {
		oMessage = accessor.newRequestMessage("GET", "http://api.twitter.com/1/account/verify_credentials.json", null);
	} catch (OAuthException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	} catch (URISyntaxException e) {
		throw new RuntimeException( "It failed to authenticate Twitter account", e);
	}

	OAuthResponseMessage rMessage = client.access(oMessage, ParameterStyle.AUTHORIZATION_HEADER);
	int status = rMessage.getHttpResponse().getStatusCode();
	if( status == HttpResponseMessage.STATUS_OK){
		String jsonStr = rMessage.readBodyAsString();
		JSONObject jObj = JSONObject.fromObject(jsonStr);
		String userId = jObj.optString("id");
		accessor.setProperty( "id", userId);
	}else{
		throw new RuntimeException( "It failed to authenticate Twitter account STATUS CODE:"+status);
	}
ここでは、認証済みユーザー自身のユーザー情報を返す account/verify_credentials APIを呼び出しています。レスポンスのJSONを扱うのには、Json-lib ライブラリを使用しています。

最後に認証情報をクッキーに保存しています。
	//Store access token and secret to Cookie
	TwitterAPIUtil.storeTokenToCookie(accessor, response);
このメソッドの中身は以下のようになっています。
public static void storeTokenToCookie( OAuthAccessor accessor, HttpServletResponse response, int maxAge){
	Cookie cookie = new Cookie( accessor.consumer.consumerKey+"_requesttoken", accessor.requestToken);
	cookie.setPath( "/" );
	cookie.setMaxAge( maxAge);
	response.addCookie( cookie);
	
	cookie = new Cookie( accessor.consumer.consumerKey+"_accesstoken", accessor.accessToken);
	cookie.setPath( "/" );
	cookie.setMaxAge( maxAge);
	response.addCookie( cookie);

	cookie = new Cookie( accessor.consumer.consumerKey+"_secret", accessor.tokenSecret);
	cookie.setPath( "/" );
	cookie.setMaxAge( maxAge);
	response.addCookie( cookie);
	
	String userId = (String)accessor.getProperty("id");
	if( userId == null){
		userId = "";
	}
	cookie = new Cookie( accessor.consumer.consumerKey+"_id", userId);
	cookie.setPath( "/" );
	cookie.setMaxAge( maxAge);
	response.addCookie( cookie);
}
認証情報は、もちろんデータベースなどのストレージに保存することも可能です。しかし、これらの認証情報は、Twitter(プロバイダ)のサイトでユーザーが一方的に無効にできることには留意しておく必要があるでしょう。

別の場所で認証情報を使ってAPIを呼び出すには、以下のようにします。
	.........
	OAuthClient client = new OAuthClient( new URLConnectionClient());
	OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
	OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
			CONSUMER_KEY, COMSUMER_SECRET, provider);
	OAuthAccessor accessor = new OAuthAccessor( consumer);
	
	//try to retrieve token
	Cookie[] cookies = request.getCookies();
	for( int i = 0; i < cookies.length; ++i){
		Cookie cookie = cookies[i];
		if( cookie.getName().equals( accessor.consumer.consumerKey+"_accesstoken")){
			accessor.accessToken = cookie.getValue();
		}else if( cookie.getName().equals( accessor.consumer.consumerKey+"_requesttoken")){
			accessor.requestToken = cookie.getValue();
		}else if( cookie.getName().equals( accessor.consumer.consumerKey+"_secret")){
			accessor.tokenSecret = cookie.getValue();
		}else if( cookie.getName().equals( accessor.consumer.consumerKey+"_id")){
			accessor.setProperty( "id", cookie.getValue());
		}
	}
	.........
非常に駆け足になってしまいましたが、どのような印象を受けましたか?ライブラリを使用することで、かなりOAuthへの敷居が下がったと感じた方もおられるのではないでしょうか?

この記事がちょっとでもだれかのお役に立てば幸いです。

続きを読む "JavaライブラリでOAuth認証" »

  [PR] 転職
ウノウラボはウノウ株式会社のエンジニア/デザイナーによる大小のアウトプットを行っていく場です。

現在ウノウは絶賛人材募集中です。詳細は求人ページへ。

著者一覧

YAMADA shintaro keita yamaoka yuki isogawa cloned uchida ishikawa chihiro kay sawa hiroki ichimaru brandon ohta murahashi takezo tomohisa sayuri yuzo hanada taka mayutan go j-nasu eitetsu jhoshina christel tokuyama shinichi

デモサービス

ライブラリなど

ライセンスについて

ウノウラボで配布しているソースコードのご利用につきましては、基本的に修正BSDライセンスに従うものとします。 それ以外のライセンスを指定させていただく場合は記事中で個別に記載いたします。

ウノウサービス

最近のトラックバック

Powered by
Movable Type 4.261