弊社エンジニア職の求人に、日本から一向に応募が無い件
先日、弊社CareerLink Vietnamにて、エンジニア職の新規求人を始めました。勤務地はベトナム・ホーチミン市。
東南アジア各国を飛び回るウェブ系エンジニアWanted!!
[5/9 03:10 追記] WantedlyのURLが古く応募ができなかったようです。新URLに置き換えました。
日本人にターゲットを絞り、満を持してWantedlyに掲載したのだけれど、応募がない。
個人的には、南国ベトナムで開発に従事するというのは、なかなか魅力的な労働環境だと思うのだけれど、どういうわけか、日本から一向に応募がない。
なぜ応募が振るわないのか、理由を考えてみたが、労働環境に魅力が無いわけではないと思う。ただ、いきなり東南アジア勤務というのが若干ハードルが高く感じられるのかもしれない。
そこで、気を取り直して、現地ベトナムの雰囲気が伝わるよう、採用に関する補足事項等を、本エントリにまとめてみることにする。
CareerLink Vietnamについて
ベトナム国内向けジョブサイト www.careerlink.vn を開発・運営している。
会社規模はどれくらいか
社員数は数十人程度、創業者CEOは日本人。また、日本人の営業担当の方が複数人働いている。他は皆、ベトナム人スタッフ。なお唯一のエンジニアであるところの僕(CTO)も日本人。創業は2006年だけれども、Webに力を入れ始めたのは今年に入ってからなので、技術人材面ではどベンチャーである。
スタッフの皆さん
なぜ日本人エンジニアを採用するのか
今回、求人のターゲットを日本人に絞った理由は2つあって、
1)やっぱり日本人エンジニアは優秀だなあと感じるから。実は今回の採用活動に至る前に、ローカルでベトナム人エンジニアの募集をしていたのだけれども、弊社の要件を満たす応募が皆無だった。皆、レジュメを盛り過ぎである。。 またWeb系エンジニアであるからには、単に言われた通りの実装をコーディングするのではなく、ぜひプロダクトの使い勝手を積極的に改善していってほしいところだけれども、そういうマインド(やセンス)を持っている人は、ここでは本当に少ない。
2)CEOとCTOが日本人だから
英語でのコミュニケーションは色々と苦労があるので。。
これまでは、僕の個人的なつてをたどって、日本在住のWeb系エンジニアの方々にお願いして、受託扱いで手伝いに来てもらったりしていたのだけれど、どうもそれでは開発が追いつかなくなってしまった。今、弊社でやりたいことを実現するには開発力が全く足りておらず、目下新規採用が至上命題。ベトナム人ではなく是非日本人のエンジニアにJOINして頂きたいと考えている。
想定している応募者像
- LAMP開発歴1年〜5年程度(PHP + MySQL)
- サーバサイドMVCのなんたるかを、それなりに理解している
- MVCフレームワークを使って普段仕事をしている
- JSもそれなりに書く
- 普段Gitを使っている
- いずれはPHP以外の言語にも手を出してみたいと思っている
- 自社サービスを手がけるエンジニアになりたいと思っている
- いずれはディレクションやマネジメントにも挑戦してみたいと思っている
新しくJOINして頂く方には、まず弊社のフロントエンドサイト(www.careerlink.vn)の拡張・改良に従事していただくことになると思う。現在、弊社フロントエンドはSymfony2フレームワークを用いて構築されている。Symfony2を触った経験があればいいけれども、Symfony2の経験がなくても、他のMVCフレームワークを扱った経験があればスムーズに着手できると思う。またSymfony2は、Eric Evans氏が提唱するDDD(ドメイン駆動設計)の考え方が色濃く反映されたフレームワークであり、開発者にはDDDに関する予備知識があることが望ましい。
ワークパーミット(労働ビザ)について
会社にて準備。
ホーチミン市での暮らしぶりについて
食事について
1)日本食
市内に日本食レストランは充実している。「なんちゃって日本食」ではなく、どれも日本人経営の、れっきとした和食である。ホーチミン市はラーメン激戦区でもある。
Lê Thánh Tông通りの日本人街にあるラーメン屋「大ちゃん」
2)ローカル料理
日本食のリーズナブル感と比べると、コスパが低いように感じる。衛生面に関して言えば、僕は胃腸が弱いほうだけれども、ローカル料理を食べたことで特に問題が起きたことはない。
3)その他外食
旧仏植民地のせいか、パンはとても美味しい。自分は朝ごはんに最寄りの「Tous Les Jours」という韓国系パン屋さんでクロワッサンを食べることが多い。この店のクロワッサンとデニッシュチョコパイはとても美味い。ただ店内にヤモリが多く生息していて(ベトナムでは一般的)、天井にへばりついたヤモリが希にウンチを落とす
4)自炊について
自分は自炊をしないので、スーパーマーケットや市場等の事情はよくわからない。スーパーマーケットはあちこちにできているけれど、日本ほど多くはない印象。バイクがないと買いだしは難しいかもしれない。
家賃について
日本と比べると安いが、物価水準を考えるとかなり高い。市内は人口密度が高く、不動産は大変貴重なリソース。外国人向けワンルームのアパートであれば200USD-250USDくらいで借りることができる。奮発して500USDくらい出すと広くておしゃれな部屋になると思う。なお外国人が借りるようなアパートは、基本的にルームクリーニング・ランドリーサービス付きである。
治安について
治安は基本的に良い。軽犯罪(ひったくりなど)は多いが、注意することで被害を防げる。スマホのひったくりには特に気をつけたほうがいいと思う。凶悪犯罪は殆ど無く、真夜中に女の子が一人でATMからお金を下ろしていたりする。
現地での交通手段について
運転免許を取得して、バイクに乗ると行動範囲が広がり、ベトナムを満喫できる。自分も免許を取ってバイクを購入した。免許を取得するのはさほど難しくないが、バイクに乗っている日本人の半数はなんと無免許である(免許を取得するのが面倒くさいみたい)。
ベトナムでバイクに乗っていると言うと、多くの外国人から「クレイジーだ」といわれる。事実、バイクの接触事故は非常に多い。ただ、市内では速度域が低いため、命に関わるような重大事故は少ない。ちゃんとしたヘルメットをかぶろう。真っ当な会社の駐在員は、大抵社則でバイクの運転が禁止されている。タクシーなどの交通手段も安く使えるので、バイクがなくても困ることはない。
朝の通勤ラッシュ
医療等について
日本語が通じる医療機関が複数あり、大抵のことは市内で事足りる。万が一、ホーチミンで手の施しようがない事態になった場合、バンコクもしくはシンガポールにヘリ移送される。なお、会社支給の邦人向け海外医療保険があり、ヘリ移送と相成っても移送費は保険でカバーされる。
市中心部サイゴン大聖堂近くの公園地帯。日本人御用達の病院が公園の隣に二件ある
日本人コミュニティについて
自分はそういう場に顔を出さないのでよく分からないが、日系フリーペーパーなどを見ていると、日本人同好会(フットサルとかソフトボールとか)のメンバー募集広告は多く掲載されている。日本人経営のバーやカフェなどもあるので、そういったお店が交流の場になっているのかもしれない...
その他
求人情報や環境の補足は以上なのだけれども、ついでなのでベトナム、というか新興国でWebサービス開発をするということについて、常日頃見聞きしていること・感じていることなど書いてみたい。
現地で一般的な開発言語について
ベトナムはIT開発系オフショアの一大拠点であり、当然ながら.Net, Java あとはとにかくPHP。去年Matzがホーチミン自然科学大学でRuby講演をした辺りから、RubyやRailsの経験を問う求人が増えてきたイメージがある。他、Drupal等のCMSカスタマイズを問う求人案件がよく目に付く。
現地Web系企業で一般的な開発スタイル
CDD - コピペ駆動開発(あっこれは万国共通かな..)
技術トレンドについて
キャッチアップは先進国の1年遅れくらいかな? 今では mongodb, redis, cassandra, neo4j (←何に使っているんだろう)などの経験を問う求人も、ちらほらと見かけるようになってきた。 ストリームデータ処理とかはまだ見かけない。
マカー率
極めて低い。理由は、
- ハードウェアが非常に高価(日本で買うよりずっと高い)
- OSがタダではない。WindowsだとPCを買えばタダで付いてくる(えっ)
あたりだと思われ。(ちなみに僕は日本で買ってきたMBA2012を使っている)
スマホ、タブレットソリューションの重要性について
カフェに入ってiPhoneもしくはiPadをいじっているのがこっちではステータス。うちも早くiOSアプリを出さねば。
カウンターでこれみよがしにiPhoneをいじる
賃金水準の違いが可能にすることについて
ここベトナムでは、4年制大学を卒業した新卒ベトナム人エンジニアで、初任給は200-300USD程度。非エンジニアのオフィスワーカーだともう少し低いと思う。
弊社社内では、顧客ユーザが入力したデータを社内オペレータが人海戦術で電話連絡、修正・加工していたり、メタデータを追加していたりするが、これは先進国では成立し得ないのではないかと思う。
外国資本同士の殴りあいについて
海外サービスを無理やり締め出して、広大な国内市場をローカルサービスプロバイダに独占させる大陸中国などと異なり、ベトナムは幸か不幸か、強力な外資系プロダクトが国内市場を蹂躙している。結果、国内IT産業が脆弱なままにある。従って、この国でWebサービスを展開する場合、競合他社は必然的に外国資本であることが多く、しかも多くの場合ベトナム以外のASEAN各国にも事業展開している多国籍企業を相手にすることになる。そういった競合と互角に渡り合うには、我々もベトナムからASEAN各国に出て行き、規模を追求しなければならない。世界規模で寡占化が進む中で、今がラストチャンスかもしれない。
このエントリを読んで、もしベトナム・ホーチミン市での開発業務に興味を持ってくれた方がいれば、是非連絡してください。直接Wantedlyで応募するもよし、疑問、質問があればメール(: kazki.matz@gmail.com)もしくはTwitter(: @KazkiMatz)に問い合わせるもよし。もちろん「まずは話を聞きにいきたい」も大歓迎。成田から飛行機で6時間、ホーチミンでお待ちしています。
[追記] 5/8 23:10
所用で、5月8日〜16日の間、関西地方にいます。関西圏の方で「話を聞きたい」という方がいらっしゃれば、直接お会いすることもできます。@KazkiMatzにメンション投げてください。
近所の旅行代理店にて。どういうわけかベトナムの猫はみんな痩せている
「fluentd」と「Storm」の比較について
まず、両者はかなり性質の異なるプロダクトなので、以下の比較は筋違い。
筋違いであることを前提に、ストリームデータ処理プラットフォームとしての両者を比べてみる。
基本情報
fluentd
http://fluentd.org/
今をときめくログコレクター/イベントアグリゲーター。Rubyで実装されているが軽量高速。
RPC基盤ではなく、その下のレイヤーに位置するプロダクト。
Storm
http://storm-project.net/
分散RPC基盤。ストリームデータ版MapReduce風フレームワーク。Java+Clojureで実装されている。
概要については、下記のスライドがとてもわかりやすかった。
Twitterのリアルタイム分散処理システム「Storm」入門
ストリームデータ処理で何をするのかについて
ストリームデータ処理のニーズについて、自分が理解している範囲での簡単な説明。
典型的なWebアプリケーションのアーキテクチャ、すなわちアプリケーションサーバプロセスがステートレスであり、データの永続化はDBのみが担うタイプのソフトウェアに、上手くおさまってくれないタイプの処理がある。具体的には、
- リアルタイムランキング
- リアルタイムトレンド分析
- リアルタイム不正監視
といったもの。
「ランキング?Redisでいいじゃない」Redisお手軽でいいですね。ただ「直近の60分間を対象にしたユニークアクセスランキング」みたいなことをしようとすると、Redisでは難しいかもしれない (時間窓を徐々にずらしていく必要があるし...)
(=> こんなfluentdプラグインを書いた: http://d.hatena.ne.jp/kazuk_i/20130506)
「トレンド分析」これはTwitterの"Worldwide Trends"機能みたいなもので、今一番ホットなハッシュタグをリアルタイムで教えてくれたりするもの。まぁ、これもRedisで事足りる... ただしトレンドを判定するアルゴリズムが、Redisが持つデータ構造で表現できるほどに、十分単純である必要がありそう(単位時間あたりのヒット数でソートした上位アイテム、とか)。
「不正監視」たとえばパスワードのbrute force アタックを防ぎたいとか、botによるサイトクロールを一定の制限を設けて遮断したいなど。過去ののアクセスパターンと直近のアクセスを組み合わせて総合的に判断する必要あり。これはRedisとかではちょっと難しそう。
つまり、状態変化が過去の入力の複雑な重ね合わせとして導かれるようなモデルは、RDBMSや各種KVS、Redisなどに載せるのが難しい。無理に載せると、アップデートのたびに大量のREADクエリを走らせることになってしまい、現実的なパフォーマンスが出ない。
ではどうするか。必要なデータをアプリケーションプロセス内部に、インメモリで保持する。
これと親和性が高そうなのが、ストリームデータ処理であり、イベントドリブンなアーキテクチャであり、そのためのプラットフォームとして世に登場したのが、例えばApache S4であったり、Twitter Stormであったりするわけで、中でもStormが一番モテている印象がある。
ストリームデータを逐次処理するという手法は、金融系のシステム等では随分前からお馴染みのアプローチだったらしい(不正監視とか)。Web業界では、ここ3−4年でマンモスサイト(TwitterとかFacebookとか)への導入が進み、そして、ここ1−2年で、そういったプロダクト(Stormとか)がオープンソース化されて、我々も気軽に使えるようになった。
Stormのスゴイところ
ある種の(大多数の?)処理が、MapReduceでほぼ無尽蔵にスケールアウトするようになったのと同様に、StormでもSpout+Boltを組み合わせることで、ある種のストリームデータ処理をどこまでもスケールさせることができる。Twitter規模のトラフィックで、トレンドハッシュタグのランキングをリアルタイムに算出するなんて用途にうってつけ。
Stormの欠点
MapReduceはどこまでもスケールする代わりに、計算効率を犠牲にしている面がある。よくある単語数をカウントするサンプルなんかは、やってる処理内容に比べて、そのオーバヘッドが極端に大きい例だと思う(まぁ実際にはネットワークやディスクIOがボトルネックなのであまり問題ではないのかもしれないけど)。同じ事がSpout+Boltモデルにも言える。
これは欠点というより、スケーラビリティを追求した結果であり必然。
fluentdのいいところ
fluentdはあくまで土管なので、Stormのように分散処理基盤を提供してくれるわけではない。しかしfluentdには入出力プラグインAPIが用意されているので、プラグインを書くことで、データフローに対して任意の処理を行うことができる。つまりその気になれば、fluentdに複雑なストリームデータ処理を載せてしまうことができる。
一方で、トランザクションやジョブトラッキング機能が必要である場合、すべて自前で実装することになる。
※fluentdストリームに対し、アドホックにクエリを登録して結果を得るというアイデアが存在した :
fluentd+Esperで動的ストリー ムクエリ
@angostura11さん作のEsper。これはスゴイ!
耐障害性について
これは、「高可用性」と「整合性」の2つに分けて考える必要があると思う。
高可用性について
整合性について
- Storm
- 上で見たように、ストリームデータ処理方式の最大のメリットが、アプリケーションプロセスがデータをインメモリに持たせられることであるとすると、『Stormを採用すれば耐障害性が担保されるので、整合性も保たれる』と考えるのは妥当なのか?例えばStormに標準で付いてくるワードカウントのサンプルプログラム、これの実装を見ると、累計カウント値は Workerスレッド内の、集計担当Boltが保持しているHashMapインスタンスに保存されている。つまり、何らかの障害が発生し、このインスタンスをホストするサーバが吹き飛んでしまえば、(以降の処理は他のWorkerが受け継いでくれるものの)当然新Boltからemitされる値はゼロにリセットされる。これを何とかするには、中間データ(上記例であれば、累計カウント値など)を、Redisのような外部DBにmemoizeしなければならない、、、がそれではパフォーマンスが出ないし、そもそもストリームデータとしてStormで処理する必然性が無い。。。
- fluentd
- 上と同じ事がfluentdでも言える。加えて、fluentdではデータがWorkerで必ず処理されることの保証が無い。
上記のような整合性リスクに関する考え方が、ストリームデータ処理を最も特徴づけるのではないかという気がしている。
自分はどうするか
弊社(CareerLink Vietnam)は業務の性質上、アクセスパターンの可視化やユーザ不正行動監視が重要。ひと月ほど前から段階的に、各種処理をストリームデータ処理として実装しなおしている。
しかし、うちのインフラは規模がかなり小さく(サーバも6台しかないし)、ストリームデータ処理のために何台もノードを割り当てることはできない。Stormの強みが活かせる環境ではない。しばらくはStormを諦めて、fluentdでささやかにストリームデータ処理しようと思っている。なにせ、fluentdならRubyで書けるし。(これが一番大きい)
"fluentd as a Poor-man's Storm"、 どうかなぁ。
fluent-plugin-uniqcountを公開しました
概要
fluent-plugin-uniqcountは、指定期間範囲において、設定で指定した属性ごとにイベント発生件数をユニークにカウントし、結果の上位N件を定期出力するfluentdプラグインです。SQLでいうところの:
SELECT key1, COUNT(key2) AS key2_count, COUNT(DISTINCT(key2)) AS key2_uniq_count FROM records WHERE time BETWEEN T1 AND T2 GROUP BY key1 ORDER BY key2_count DESC LIMIT 0, N;
に相当します。
fluent-plugin-uniqcountを使うと、
などが集計可能です。
特徴
fluent-plugin-uniqcountは、単位時間あたりのトラフィック量をN、集計対象区間長(設定項目: list*_span)をTとしたときに、以下のような特徴を備えています。
- イベント1件あたりのプラグインへの入力コストは O(logNT)。単位時間あたりだと O(N logNT)。
- プラグインの出力コストは O(logNT)。
- 消費メモリは O(NT)。
- ユニークカウントである(F5リロードなどでランキングが跳ね上がらない)。
- 直近(T秒前〜現在)の集計結果が可能である(集計対象区間が時間経過とともにスライドする)。
- 一つの集計単位を「リスト」と呼び、独立した設定を持つ任意の数のリストを保持することができる(最大10個まで)。
- fluentdのイベント受信時刻ではなく、レコード内のタイムスタンプを基に集計を行うことができる(未設定時はイベント受信時刻を用いる)。
- appサーバからfluentdにイベントが到達する時間のゆらぎを吸収するため、集計対象区間の終点を現在時刻から過去へオフセットすることができる(list*_offset: 転送に関与したfluentdのflush_interval値を考慮して設定する)
設定
項目
- list*_label : 出力レコードに付与されるラベル文字列
- list*_time : イベント時刻フィールド名(UNIXタイムスタンプ形式 - 省略時はfluentdにおけるイベント受信時刻が使われる)
- list*_key1 : グルーピングに用いるフィールド名
- list*_key2 : カウント対象フィールド名
- list*_span : 集計対象区間長(秒)
- list*_offset : 集計対象区間終点のオフセット値(秒)
- list*_out_tag : 出力レコードに付与されるタグ
- list*-out_num : 出力件数(上位N件)
- list*_out_interval : 出力間隔(秒)
設定例
入力イベントが以下のようなフォーマットを持っているとき、
site.access_log: {"at":1367820029,"uri":"http://www.careerlink.vn/","remote_ip":168364289} ...
次のような設定で、各spanにおけるURLランキングを得ることができます。
<match site.access_log> type uniq_count list1_label min list1_time at list1_key1 uri list1_key2 remote_ip list1_span 60 list1_offset 3 list1_out_tag trends.min list1_out_num 5 list1_out_interval 1 list2_label day list2_time at list2_key1 uri list2_key2 remote_ip list2_span 86400 list2_out_tag trends.day list2_out_num 5 list2_out_interval 10 </match>
出力例は以下のようになります。
trends.min: {"label":"min","ranks":[{"key1":"http://www.careerlink.vn/","rank":0,"key2_uniq_count":13},{"key1":"http://www.careerlink.vn/file/71ca7183087cce9f04fc559ce37738e9","rank":1,"key2_uniq_count":12}, { ... }, ...],"at":1367840734} trends.day: {"label":"day","ranks":[{"key1":"http://www.careerlink.vn/","rank":0,"key2_uniq_count":8034},{"key1":"http://www.careerlink.vn/file/065d88676460f47d99ec59263c650f54","rank":1,"key2_uniq_count":7735},{"key1":"http://www.careerlink.vn/file/71ca7183087cce9f04fc559ce37738e9","rank":2,"key2_uniq_count":7266}, { ... }, ... ],"at":1367840734}
TODO
- gem化
- 省メモリ化のため、オブジェクトの生成数をもっと減らしたい
Backbone.jsを利用したクライアントサイドMVCの導入についてそろそろ書いておくか
jQueryヘビーなアプリケーションの問題点と、MVCによる構造化の必要性
jQueryは、ブラウザ上で動くJSアプリケーションの開発生産性を劇的に向上させました。DOM操作による動的なページ書き換え処理などは、セレクタを使ってちょろっとコードを書くだけで、ほんの数行で記述できてしまいます。
しかし、この方法の延長で、大規模なJSアプリケーションを構築することは果たして現実的でしょうか。例えば「GMail」や「New Twitter」程度の規模のJSアプリケーションを書かなければならないとしたら、どうでしょう?
大規模なJSアプリケーションを開発するには、こういった手法を延長するのではなく、より洗練されたデザインパターンを導入する必要があります。この目的にぴったりのデザインパターンが、「MVC」デザインパターンです。
MVCパターンは、Webの世界ではサーバサイドプログラミングで広く知られており、Ruby On Rails、Django、Struts等、MVCの考え方に則ってデザインされたサーバーサイドプログラミングのフレームワークが多数登場しています。一方で、クライアントサイドでのJSアプリケーションにおけるMVCパターンの活用は、まさにこれから旬を迎えようとしています。ここ数カ月間で、クライアントサイドでMVCを実現するためのJSフレームワークがいくつか登場しました。
現在、クライアントMVCのためのJSフレームワークとして、KnockoutやJavaScriptMVC、そしてBackbone.jsが広く知られています。本エントリでは、その中でも特に有望(※主観です)と思われるBackbone.jsを紹介しつつ、クライアントサイドMVCとサーバサイドMVCの本質的な違い、Backbone.jsを用いたアプリケーションの実装方針について考えます。
なお、Backbone.jsが最近アツいとはいえ、まだまだドキュメントやサンプルコードの整備は発展途上であり、フレームワーク自体の自由度も高いことから、Backbone.js使用時における設計の「正解パターン」は未だコンセンサスが無いように見受けられます。したがって、本エントリに記述してある考え方の大半は、あくまで僕個人が「Backbone.jsはこう使えばいいんじゃないかと思った」というレベルのものであり、オフィシャルな裏付けは特段無いばかりか、Backbone.js開発陣の思惑と乖離している可能性も十分にある旨、ご承知おきください。
参考:Knockout vs JavascriptMVC vs Backbone
ここまで前置き(;´∀`)
Backbone.jsのリファレンス等
参考記事
- Backbone.js - A brief introduction (4/7 18:55追記)
Backbone.jsの端的なまとめスライド資料。本エントリに似た趣旨の内容がコンパクトにまとめられている。
エントリ前半の概論を読むと、Backbone.jsのイメージがわきやすい。後半のサンプルは参考になるが、抜けや間違いが多く、そのままでは動かないと思われ。
Backbone.jsに対する考察が参考になる。
Backbone.jsとは
Backbone.jsは、JSヘビーなクライアントサイドJSアプリケーションを書くために作られたJSライブラリです。Backbone.jsを導入することで、開発者は自分が実現したい処理を、Model、ViewそしてControllerの組み合わせで実装しなければなりません。
Backbone.jsはUnderscore.jsに依存しています。
Backbone.jsはjQueryを置き換えるものではありません。Backbone.jsはjQueryより上位の概念として存在しているライブラリです。Backbone.jsはjQueryには依存していませんが、View内でのDOM操作はjQueryを用いるのが便利であるため、併せて導入したほうがよいでしょう。現在オンラインで見ることが出来るBackbone.jsサンプルコードのほとんどがDOM操作にjQueryを用いているようです。また、MustacheのようなテンプレートエンジンをView内で用いることも推奨されています。テンプレートエンジンを導入することで、View内でのHTML生成コードをスッキリ記述することができます。
以下は、Backbone.jsが提供する主だったクラスです。
(1) Backbone.Model
いわゆるModelクラス。データの保持や操作を一身に引き受けるクラス。
(2) Backbone.Collection
Modelクラスの一部機能を切り出したもの。Modelオブジェクトの順序付きリストとして機能する。サーバサイド等にデータを永続化する際のCRUDオペレーションも担当する。
(3) Backbone.View
いわゆるViewクラス。HTMLのレンダリングを担当する(レンダリング処理を行うメソッドはrender()という名前にすることが多い)。DOMで発生したイベントに対して、簡単にハンドラをバインドできる便利メソッド「events」等が用意されている。View内部でjQueryを併用するとDOM操作がやりやすくなる。また、Mustache等のテンプレートエンジンを用いると、HTML生成コードがスッキリ記述できる。
クライアントサイドJSアプリケーションはイベントドリブンなので、Viewはイベントハンドラの記述等で必然的にヘビーになりがち。Viewの中のrender()メソッドが、狭義での(サーバサイドMVCにおける)Vに相当する。
(4) Backbone.Controller
いわゆるControllerクラス。URLフラグメント(URLのうち、#以降の部分)に応じたルーティング処理を行うことができる。
Backbone.js下では、Controllerを経由しない、ViewからModelへのアクセスが多発する。これはイベントドリブンな挙動を実現するため、またRESTfulに忠実なURI設計を行うため(後述)。このため、サーバサイドMVCのコントローラに比べると、Backbone.jsのコントローラはスカスカになりがち。
(5) Backbone.History
Controllerクラスの一部機能を切り出したもの。ルーティング周りを担当する。
サーバサイドMVCとクライアントサイドMVCの違い
デザインパターンとしてのMVCには、分野に応じて様々な定義があるようです。Web系開発者にはおなじみのサーバサイドMVCとは違って、Backbone.jsは、OSネイティブのGUIアプリケーションに近いMVCを提供します。サーバサイドのMVCと、Backbone.jsを用いたクライアントサイドのMVCを、図で比較してみましょう。
左右のMVCパターンを見比べたとき、左と比べて右側が異なっている点はどこでしょうか。
(1) ViewとModelが結びついている
Backbone.jsではDOMへのイベントハンドラをView内のメソッドとして記述します。このためViewがかなりヘビーになります。さらに、View内に記述されたイベントハンドラが直接Modelを叩くため、ViewとModelが密結合になります。
OOPが重要視するクラス再利用の観点からは、ViewとModelが密結合していることは好ましくありません。このため、Backbone.jsでは、Backbone.Eventsというモジュールを内部で導入し、デザインパターンでいうところの「Observerパターン」を提供することで、この問題を軽減しています。
Backbone.Eventsの導入により、Modelは自分を利用するViewについての予備知識を持つ必要がなくなりました。しかし、Viewは自分が利用するModelについて知っている必要があります。これについてはクラス再利用の観点から許容できないと感じる人もいるかもしれません。DOMイベントに対するイベントハンドラを、Controller(もしくはそれに準じた専用クラス)に分離することで、ViewからModelへの直接のアクセスを排除することはできそうではありますが、コード量が増加する割に複雑性は対して低減せず、メリットが薄いものと個人的に考えています。シンプルさを保つためにもViewがModelを直接叩いたほうがよいのではと思います。
(2) Controllerの役割が薄い
Backbone.jsのControllerは、基本的には、与えられたURLフラグメント(http://foo.com/bar#zoo/baz のようなURLで、ハッシュ以降の"zoo/baz"部分のこと)に応じた処理のルーティングのみを担当します。このため、ページ遷移(この場合のページとは、ブラウザの再読込を伴わず、URLフラグメントのみが書き換わるような画面遷移を意味します)が無いような小規模なアプリケーションでは、Controllerがスカスカになるかもしれません。一方で、提供する機能に応じてページを切り替えるアプリケーションでは、ページに応じたルーティングをControllerが担うことになるでしょう。
たとえば、GMailの場合、受信トレイのURLは
https://mail.google.com/mail/?ui=2&shva=1#inbox
送信トレイは
https://mail.google.com/mail/?ui=2&shva=1#sent
コンタクトリストは
https://mail.google.com/mail/?ui=2&shva=1#contacts
といったように、機能(リソース)に応じてハッシュ以降を変更することで、ページ再読込を伴わないAjaxアプリケーションにも関わらず、各リソースに対するRESTfulなURL(※本エントリの最後に注釈を追記しました)を実現しています。これのおかげで、URLによりアプリケーションの状態を復元することができ、Ajaxにも関わらずブラウザの「進む」「戻る」ボタンもごく自然に機能してくれます。Backbone.jsのControllerを活用することで、自作のアプリケーションにこういったURLを極めて簡単に導入することができるのです。
また、一般的にサーバサイドMVCでは、ControllerはViewをキックする役割を担っていることが多いでしょう。Controllerの役割は、Modelから必要なデータを取得し、それをViewに与え、ViewをキックしてHTMLをレンダリングさせます。一連の処理で、常にControllerが中心で仕切っている構図です。このため当然、ControllerはViewの在処を知っている必要があります。
一方、クライアントサイドMVCでは、事情はかなり異なります。クライアントサイドMVCでは、ViewはModelのデータをリアルタイムに反映し続けなければなりません。この点、バッチ処理的に一度HTMLを生成すれば役割が終了したサーバサイドとは違います。このため、ViewはModelが持つデータの変更イベント(Backbone.jsではchangeイベント)を拾い、イベント発生のたびに自らを書き換えるようにします(Observerパターン)。このため、ControllerとViewの間に直接的な関係は生じません。むしろModelとViewが密に連動しているのです。Controllerはあくまで、アプリケーションが提供するリソースの見え方を変化させるキッカケを与えるだけの役割しか担っていないのです。
Backbone.jsがユーザ入力イベントを処理する二つのルート
Backbone.jsでは、ユーザが発生させるイベントを処理するために、二つのルートが想定されています。開発者は、処理の内容に応じてこれら二つを使い分ける必要があります。
(1) Controllerが提供するルーティング機能
URLのハッシュ以降で表されるURLフラグメントを元に、Controllerがアプリケーションの状態を変更する。ページ要素の多くを書き換えるような遷移にはこの機能を使う。(例:GMailにおける受信トレイから送信トレイへの移動等)
(2) Viewが提供するdelegateEvents
Viewが提供する、イベントハンドリングの仕組み。Modelデータの更新や、ページ要素の一部が書き換わるような小機能の実装には、Viewが提供するこの機能を使う。(例:GMailにおける、下書きメールの編集更新、オートセーブ機能、フィルタ設定画面の表示)
Backbone.jsを使ったアプリケーション設計のポイント
(1) ModelはViewを意識してはいけない(ModelがViewを呼び出す際にはObserverパターンを使う)
前述のとおり。Modelは再利用可能でなくてはならず、特定のViewや特定のControllerに依存するようなコードを、Model内に混ぜてはならない。
(2) ViewはModelを直接叩いてもよい
前述のとおり。
(3) ControllerはRESTfulに
URLフラグメントはあくまでURLの一部であるから(※本エントリの最後に注釈を追記しました)、RESTfulに設計しなければならない。フラグメントに「アクション」を記述してはならない。URLが表すものは、あくまでリソースの場所情報であるべきである。
(悪い例)http://foo.com/bar#delete_comment/3 ← URLに「アクション」情報が含まれており、RESTfulでなくなっている。このURLはURLとして意味をなさない。また、このURLを再読込するとヘンなことになってしまうだろう。こういったアクションはdelegateEventの枠組みで、ViewがModelに直接依頼することで処理されるべき。
(良い例)http://foo.com/bar#latest_comments/orderby-dateasc-limit-10
オンラインで入手可能なBackbone.jsサンプルコードは、いずれも小規模なアプリケーションであることが多い。こういったアプリケーションではページ遷移が発生しないため、ルーティング機能解説のためか、無理やりURLフラグメントにアクションを記述しているものも多く見られる。こういった部分は真似しないほうが良いだろう。
(4) ViewはModelの粒度にあわせて階層化/細分化する
ViewとModelはそれぞれ一対一に対応しているのが望ましい。ViewとModelを対応させることで、あるModelが更新されchangeイベントが生じると、対応するViewが自動的に書き換わるような設計が採用しやすい。また、DOM書き換えの範囲を最小限にすることができるため、描画高速化に寄与する。
Backbone.jsまとめ
(1) クライアントサイドMVCは、サーバサイドMVCと比べ勝手が違う
Web系エンジニアにとって、MVCは馴染みが深い考え方です。しかし、クライアントサイドにMVCを導入するにあたっては、サーバサイドとの差異が多くあるため、サーバサイドで得た先入観を捨ててかかる必要があるでしょう。
(2) Backbone.jsでは、View→Modelという密結合が生じるが、しかたがない
前述のとおりです。ViewはControllerを経由せずに、Modelに直接アクセスをすることになります。しかし、Modelの再利用性を高めるため、ModelからViewへのアクセスは、原則としてObserverパターンを用いて実装されることになります。
(3) URLフラグメントはRESTfulに設計する
アクションをURLに含めてはいけません。URLでアプリケーションの状態を表せることは、アプリケーションにとって非常に大きなメリットになります。URLをRESTful*1に設計することにより、Ajaxアプリケーションにも関わらず、ページのブックマークが可能になり、ブラウザを再起動させても復元したタブに以前と同じ画面が表示され、ブラウザの「進む」「戻る」ボタンもユーザの直感どおりに動作するのです。
大規模なJSアプリケーションを構築する上で、MVCパターンの導入は非常に大きな助けになります。Backbone.jsのようなフレームワークの登場により、クライアントサイドでのMVC導入がこれまでになく簡単に実装出来るようになりました。
現在、この分野で参考になるドキュメントやサンプルコードは、まだまだそれ程多くはないようですが、スマートフォン全盛期の今、クライアントサイドで動くリッチなJSアプリケーションの需要は高まる一方でしょう。今後数カ月で、状況は劇的に改善していくものと思われます。
今は皆が試行錯誤を繰り返している段階であるため、Backbone.jsはユーザの自由度が非常に大きい設計になっているものの、現状のコミュニティの活況を見るにつけ、Backbone.jsを用いたアプリケーション開発の「定番デザイン」が見出されるまで、それほど時間は要さないのではないかと思います。
*1:4/8 5:03追記 はてぶコメントで指摘頂きましたが、#以降のURLフラグメントは厳密な意味でのURL構成要素ではないことを考えると、URLフラグメントでアプリケーション内リソースを表現する手法を「RESTful」と呼ぶのは間違いであることに気づきました。他にどういう言葉を使うのが適切なのか分かりませんが...
異形の廃墟ビル『メタボ岡崎』に引っ越しました
一目見たら一生忘れることのできない異形の廃墟建造物、"メタボ岡崎"に引っ越したので、その魅力を写真でレポートしたいと思います。
夕日に映えるメタボ岡崎。左京区聖護院はとても住みよいところ。
とにかく外見にインパクトがある。
室内。とても窓が多くて開放的ながら、レイアウトがそこはかとなく奇妙である。
写真からはアラが見えにくいかもしれないけれども、外側も内側もボロボロの建物なので、家賃が非常にリーズナブル。
最大の特徴は全室にそれぞれ2つ用意されている出窓。各部屋には3ヶ所出っ張りがあり、そのうち2つが出窓に、残る1つはベランダにアサインされる。下層の部屋と入れ違いになるように割り振られるので、外から見ると凸凹して見える。
メタボ岡崎航空写真。二棟をエレベータ-階段エリアで接続した構成。室内からは、向こう側の棟が谷の向こうにある島の様に見える。
メタボ岡崎は非常に多くの間取りパターンが用意されており、これはあくまで一例。僕は3部屋見せてもらったけれども、全て大幅に違うレイアウトになっていた。(ひょっとして全室間取りがユニークだったりするのだろうか?)
中にはこんな部屋も(僕の部屋ではない)。前の住民が柱に絵を描いていった。
角が非常に多い特徴あふれるデザインは、60-70年代の黒川紀章らによるメタボリズム運動を汲んだ物。建物名の由来にもなっている。充填率を優先させておらず、全室が角部屋になるような構成が採用されている。建築当時は人気のマンションだったこと[要出典]も納得の、非常にぜいたくなスペースの使い方である。
全体として異様に対称性にこだわった印象を受ける。例えば建物の中央部、一基のエレベータを挟んで両側に階段が配置されている。合理性に欠けるが、たいへん美しい。
メタボ岡崎研究家の@nanki氏曰く『この建物には至る所に黄金比が隠されている』
氏は現在、メタボ岡崎3Dモデリングデータの構築を手がけており、完成の折には建物に隠されたさらなる秘密が明らかになるかもしれない。3Dモデルの一日も早い完成及び徹底した分析が待たれるところである。
下の画像は、@nanki氏の手により、驚くべきクオリティで再現されたメタボ岡崎3Dモデリングのスクリーンショット。氏曰く「今までの人生で手がけてきたモノの中で、一番クオリティが高い制作物になってしまった」 本データを元に、FPSゲーム「MO(MetaboOkazaki)」やLEGO版「メタボおかざき」の開発等様々な企画が有志により手がけられる予定。
なお姉妹マンションとおぼしき『メタボ広沢』が右京区にあるらしい。同様に老朽化が進み、某TV番組では心霊スポットとして紹介されていたとか。実に失礼な話である。外見は類似点も多々あるものの、シルエットに大幅な差異が見られる。メタボ広沢は75年施工、メタボ岡崎は80年施工なので、メタボ広沢で得たフィードバックがメタボ岡崎に投入されたのだろうか。
(メタボ広沢)
僕の部屋、契約前に下見をしたところ、室内バスルームには鳥の糞が散らばっていた。ハトが住んでいたらしい。(その後管理人さんにクリーニングしてもらった)
今日の午後、階下の廊下からラジオ放送のような音がするのでなんだろうと思ったら、女性アナウンサーの無機質な声でなにか(乱数表?)を読み上げているとおぼしきハングルの放送が、とある一室から漏れ出ていた。一体誰が住んでいるんだろう。一昨日は突然廊下の火災報知ベルが鳴り出し、数秒後に停止した。
室内にある謎の非常ボタン。メタボ岡崎歴が長い人に聞いてみたが、押すと何が起こるのかは知らないらしい。
付近一体には、廃墟スポット特有の独特な空気が漂う。時間が止まっている感じ。人気がない。チェルノブイリのようだ。
メタボ岡崎入り口。建物名を記した碑があるが、時の洗礼を受けツタが絡まり、文字が読めなくなっている。
地下一階にはなかなかイケてるジャズ・バー「ZAC BARAN」がある。お酒も料理もとても美味しい。
ZAC BARANでとある住民の方に借りた本。Geekや芸術家肌の個性的な人が住んでいる。
メタボ岡崎の魅力はまだまだ語り尽くせないほどある。空室がまだあるようなので、左京区内で引越しを検討している方、如何ですか :)
IIR輪講@はてな "21 Link Analysis"
IIR輪講@はてなで、21 Link Analysisの発表を担当してきました。
使用したスライドは以下です。
IVS Launch Padに登壇しました
12月8日に開催されたIVS2010 Fall Kyoto Launch Padに登壇させていただき、Lingoの紹介をさせていただきました。
第2位の評価を頂くことができました。ありがとうございました。
引き続き頑張っていきます。
発表資料は以下です。
発表は動画レコーディングもされていたようです(゜O゜)