Gitをバックエンドに使ったプログラマ向きWiki - Gitit
Tweet
Wikiというものはとても便利なんだけど、
- 大量の文章を書くにはWebブラウザのインターフェースはまだまだ辛い
- オフラインで使えない(文章書くのは電車が一番)
- 複数の文章を再構成したり、一括で検索したり、置換したりは、Webだとやっぱりきびしい
と言った欠点がある。
とは言え、誰でも気軽に編集できるWikiの魅力も捨てがたい。
そこで、「Wikiではあるんだけど、ローカルでも自分の好きなエディタで簡単に編集できるツールないかなー」と探してみたら、 Gitit というWikiを発見した。
ここ数日、結構な量のドキュメントをGititで書いてみて、わりと満足しているのだけど、検索してもGititの日本語の情報があまり出てこないので紹介してみる。
Gititの特徴
- コンテンツをGitのレポジトリに保存する。
- そのGItレポジトリをcloneして好きなようにいじってからcommit/pushすれば即座にWikiに反映される。
- Wikiとしてはとても普通。マークアップはmarkdownがデフォルトだけど他にも選べる。各種言語のコードを綺麗に色付きでフォーマットしてくれる。
- Haskell製でpluginとかもHaskellで書く。
- Webサーバを内蔵している。
- 独自のメタ情報のようなものを持たずに、Gitレポジトリ内のディレクトリ/ファイル構成がそのままWikiになる。ファイル名がそのままWiki名になる。もしやめたくなった場合に他のツールへの移行も楽。
- テキスト以外の画像とかもそのままGitレポジトリに入る。もちろん、cloneしたレポジトリに画像を入れてcommit/pushすればアップロードされる。
- Gitそのままなので分散Wikiみたいのも簡単に作れるし、branchを使ってのドキュメントの管理とかもできる。
Github製の gollum と似ているが、Gititの方が良さそう。
デモサイトはこちら。 Wikiとしては、普通中の普通なので面白くないけど。
Haskell製と知って、インストールめんどくさそうだなーと思ったんだけど簡単だった。
簡単な導入手順 – Ubuntu編
$ sudo apt-get install ghc $ sudo apt-get install cabal-install$ cabal update $ cabal install pandoc gitit -fhighlighting --reinstall (pandocとgititを同時にinstallしないとhightlightが有効にならなかった)path に ${HOME}/.cabal/bin を追加$ mkdir ~/hogehoge $ cd ~/hogehoge $ gitit --print-default-config > gitit.conf $ <editor> gitit.conf $ gitit -f gitit.conf
これで、port 5001で起動するので、 http://localhost:5001/ とかで。外に公開するなら、apacheとかnginxでreverse proxyすればいいだろう。
コンテンツを直接いじりたい場合は、
git clone ssh://example.com:~/hogehoge/wikidata
とかで、普通にcloneして後はお好きなエディタでファイルを編集して、commit/push すればその場で反映される。
こんな感じで、Gititを使うと、gitを使ったプログラミングと同じフロー/リズムでドキュメントを書けるところが気に入ったのでした。
オススメ。
Google App Engine / Python 上での開発で最初から知ってればよかった、ってことをいくつか
Tweet
ここ数ヶ月、Google App Engine/Pythonを使い、初めてちょっとしたものを作ってみているのだけど、開発初期から知っておけばよかったなー、と思うノウハウ/tips的なものをずらずらと書いてみる。
基本的な環境設定は、 以前書いた まま。
0. 公式ドキュメントを良く読む
言うまでもなく、だけど、 マニュアル はもちろん、 この辺 の下の読み物も、流し読みだけでもしておいたほうがいい。
datastoreとmodel的なところ
1. key nameを使いこなす
key nameは、レコードの作成時に指定できる(RDBでいう)primary keyの別名みたいなもの。primary key自体は自動的で作成されるので開発者が指定できるのはkey nameだけ。
key nameをうまく使うことで、datastoreを使いやすくすることができる。特にdatastore上で"unique"を表現したいときに活躍する。
たとえば、 Modelクラスのget_or_insert では、key nameを使うことで簡単にuniqueなレコードを作成することができる。get_or_insert()と言う名前通り、 key nameで指定したレコードが存在したらget、しなかったらそのままinsertといういうことができる。この処理はatomicに行われるので、同じkey nameのレコードが複数存在してしまうこともない。
get_or_insert()の注意点としては、 get時には渡した値ではupdateされない、ということにということがある。そのまんまget or inssert。
2. どういうkey nameを使うべきか
- 他システムから取り込んだレコードでuniqueとされている値
例えば、twitter botとか作るなら(作ってないけど)、twitterのユーザIDとか、tweet毎のIDとかそのまま key nameに入れてしまうのが良さそう。
今作っているシステムでは、別システムのMongoDB上で作ったデータをそのまま取り込んでいる部分がある。そういうデータは MongoDBのBSON ID を文字列にしたのをそのまま key nameにしている。なかなかいい感じ。
- クライアント側で生成されたデータ
クライアント側で生成されるようなデータをdatastoreに格納する場合、クライアント側でUUIDみたいのを作成し、それをそのままkey nameにするのもなかなかよさそう。
このUUIDをkey nameにしてget_or_insert()することで、クライアント端末やネットワークの不調等で同一データが2回POSTされたりしても、データが二重になったりしないのがなかなか気に入っている。もちろんUUIDを偽造されても問題ないような作りにしておく必要はあるけど。
- 複合キー
key nameは一つしか設定できないのだけど、ただの文字列なので上記の値を結合して関連テーブルを作成したりできる。これは場合によってはなかなか便利。
- その他
uniqueっぽいものがないモデルの場合でも、とりあえずランダムで長めの文字列を指定しておくとよいかも。後からは指定できない。
3. GQLで key nameで検索
key nameでのオブジェクトの取得は、基本的には、 get_by_key_name を使えばいいのだけど、WebのadminコンソールとかGQLしか使えないようなところでは、こんな感じに書ける。
WHERE __key__ = KEY('FooModel', 'cff0611529520910195ed357dd69f908')
4. get_or_insert() でgetされたかinsertされたかを知る
get_or_insert() を実行した結果、getされたかinsertされたかがそのままでは簡単にはわからないのが、これを知りたいときがある。例えば「get_or_insert()してgetだった場合には〜する」みたいなとき。
こういうときのために、次のようなclass methodを作っている。
@classmethod
def get_or_insert2(cls, key_name, **kwds):
def txn():
entity = cls.get_by_key_name(key_name, parent=kwds.get('parent'))
if entity is None:
entity = cls(key_name=key_name, **kwds)
entity.put()
return (True,entity)
return (False,entity)
return run_in_transaction(txn)
または、 insertに成功した場合だけオブジェクトを返し、既に存在する場合はNoneを返す処理
@classmethod
def insert_or_fail(cls, key_name, **kwds):
def txn():
entity = cls.get_by_key_name(key_name, parent=kwds.get('parent'))
if entity is None:
entity = cls(key_name=key_name, **kwds)
entity.put()
return entity
return None
return run_in_transaction(txn)
5. unique制約
datastore上で、unique制約を作るのはちょっと大変。ユーザ情報を格納するUserモデル内でemailアドレスをuniqueにしたい、とか非常にありがちなことが簡単にはできない。
こういう場合にも、key nameがうまく使える。unique制約を保持するためのモデルを別に作って、uniqueにしたいデータ(この場合emailアドレス)をkey nameにしてしまうのがいい感じ。上記の、insert_or_fail で作成してみて、作成できたらそのemailアドレスは使える、という感じ。(何か他に定石がありそうなんだけど。)
6. データの一括更新
システムのアップグレード時など、データを一括で更新したくなるときがある。SQLだとUPDATE一発みたいな更新でも、datastoreだとちょっと大変。こういうときは map/reduce を使うと素早く簡単に全件の更新ができてよい感じ。(件数多いと、CPU時間==お金を一気に消費しちゃうけど)
(このappengine-mapreduceはmap/reduceってか、単に全レコードに対して並列で何かができるだけなんだけど。本当のmap/reduceはそのうち導入されるらしいので楽しみ。)
7. ランキング
ちょっとしたゲームっぽいものを作ると「スコアランキング」みたいなものが必要になったりするものだけど、このランキングってのはRDBでもそれなりに厄介なもので、何も考えないで作ると、大量のアクセスが発生してしまい使いものにならなくなる。その結果、バッチ処理で静的なランキングを作成するとかちょっとダサい感じになってしまいがち。
で、ちょっと自分で色々なアルゴリズムで実装してみたりしてたのだけど、便利なライブラリを見つけてしまったのでそれを使っている。 このライブラリ。
8. 複数レコードの保存/削除は db.put() / db.delete() を使って一括で行う
スピードが全然違う。db.delete() はクエリをそのまま突っ込んだりもできる。
9. 全文検索
全文検索は、現在の Google App Engine ではまともに実装されていないらしい。(Googleのくせに! 近い将来実装されると発表されたが)
ただ、簡易的には SearchableModel が使える。使い方は、 コードの中のコメント が一番詳しいような気がする。
そのコメントにあるように、かなり低機能で日本語も使えない。 ただ、とりあえずそれっぽく動かすだけなら簡単に使える。
ログ関連
10. loggingを使ってログを取る
logging moduleを使うことで、実行時に色々なログを取れる。Google App Engineともうまく統合されていて、Web上のadminインタフェースで、httpアクセス毎に参照できてとても便利。
11. POSTされたデータとそれに対応するresponse bodyをすべてログに
loggingを使ってPOSTの中身とresponseをそのままログに取っておくとデバッグ時に便利。Google App Engineの管理画面からアクセス毎にPOSTとそれに対応するresponseの内容を見ることができる。
これをするためのMiddlewareを書いた。(実際には 前回 書いたようにセッション管理用の SessionMiddleware も挟んでいる。)
class PostLoggerMiddleware(object): def __init__(self, app): self.app = appdef __call__(self, env, start_response): request = webapp.Request(env) if request.body: logging.info("request: " + str(request.body))def my_start_response(status, response_headers, exc_info=None): write = start_response(status, response_headers, exc_info) def my_write(body): logging.info("response: " + str(body)) write(body) return my_writereturn self.app(env, my_start_response)application = PostLoggerMiddleware(webapp.WSGIApplication([ ...
12. exceptionをtrackback付きでログに
基本的に何もしなければ、Traceback付きでログに乗る。しかし、コード側で、 handle_exception を定義してexceptionをすべてcatchして処理を続けたいときもある。
たとえば、現在JSON APIをGoogle App Engine上で作っているのだけど、例外が起きた時もJSONでエラーを返すことにしている。
こういう場合には、その中で JSONを返す処理を書きつつ、logging.exception(exception) と書いておくと、admin画面上では普通にTraceback付きでエラーが見える。
その他
13. 自分のアプリを複数バージョン同時に持てる。ただし datastore は共通。
最初知ったときは、datastore共通で何に使うんだよ、と思ったんだけど色々便利なこともあった。
- 新バージョンのテスト。わりとメインの使い方か。でも、datastoreは一個なのであまり思い切ったことはできない。
- バージョン毎にログが別になる。Google App Engineのadmin画面はバージョン毎なので、ログも別々になる。map/reduceとかを同じバージョンでやるとログがそれで溢れてしまってうざいことがあるんだけど、それも別にしておくといいかもしれない。
- propertyの型を変えるとかちょっとしたmigrationをするときに別のバージョンでやるのがいい(下記参照)。
14. 非同期でいい処理にはできる限り Task Queue を使う
リアルタイムでなくていい処理は全部 Task Queue に回してしまってもいいぐらいかも。
主な理由は二つ。
- HTTPアクセスの処理を早く終わらせるため
これはもちろんユーザのために、という意味もあるが、Google App Engineの設計ではとにかく早く処理を返すことがスケール面でも有効らしいので大事。
- リトライしてくれる
Google App Engineはそれなりに不安定なので、Google App Engineの都合でエラーがそれなりの頻度で起こる。そのさい task queue に入れておけばGoogle App Engineが直るまでretryしてくれる。
特に、mail送信などのAPIはしばしばtimeout (例えばこんなエラー: “DeadlineExceededError: The API call mail.Send() took too long to respond and was cancelled”) とかするので、これは必ず task queue に入れておいたほうがよさそう。メールは元々非同期で問題ないので相性もいい。
15. Modelのpropertyの型の変更
datastore自体はどんな型でもレコード毎に柔軟に持てるのだけど、その上で動くModelクラスは型にとても厳しくなっている。floatからintegerへなどの型も自動的には変換してくれない。その上read時にexceptionを出してくれてしまうので、結構厄介。
運用を初めてから型を変更する必要がでてきた場合には、以下の方法がいいみたい。
db.Expando クラスを使って値を更新する。db.Expandoクラスは、Modelのサブクラスなんだけど、設定していないpropertyも読み書きができる。また、システムを止めずに更新したい場合には、アプリの別バージョンを作成し、その上で行うと影響をあまり与えずにできる。
例:
class HogeUser(db.Model):
foo = db.StringProperty()
bar = db.StringProperty()
score = db.FloatProperty()
このscoreをfloatからintegerに変更したくなったとする。
こういう場合には、このクラスを一時的にdb.Expandoから継承するようにして、かつscoreを削除してしまう(データ自体は消えない)。
class HogeUser(db.Expando):
foo = db.StringProperty()
bar = db.StringProperty()
# score = db.FloatProperty()
そして、以下のようなmap/reduceを走らせる。
def alter_score_to_int(hoge_user):
if "score" in hoge_user.dynamic_properties() and type(hoge_user.score) is float:
hoge_user.score = int(hoge_user.score)
hoge_user.put()
終わったら、元に戻して Int に変更する
class HogeUser(db.Model):
foo = db.StringProperty()
bar = db.StringProperty()
score = db.IntegerProperty()
16. エラーをメール通知
http://code.google.com/appengine/articles/python/recording_exceptions_with_ereporter.html
標準で入っているこのライブラリを使うと、発生したエラーをdailyでまとめてメールしてくれる。それほど高機能ではないが、同じようなエラーはまとめるという最低限なことはしてくれる。自分でメールの内容をカスタマイズもできるみたい。
こんな感じで最初の方に書いている。ローカルでのSDK環境の場合、エラーになってしまうのでスキップしている。
in_local_sdk = (os.environ.get("SERVER_SOFTWARE") == "Development/1.0") or os.environ.get("TERM")
if not in_local_sdk:
from google.appengine.ext import ereporter
ereporter.register_logger()
その他の設定は、上のリンク先のまま。
17. 死活監視
Google App Engine上のアプリの死活監視をする場合の注意点として、Google App Engineが不安定になっている場合、readはokで、writeだけがエラーになるケースがあるようなので、writeが発生するような処理で監視をしたほうがよさそう。
参考にしたところ
http://www.mail-archive.com/google-appengine@googlegroups.com/msg20008.html
http://stackoverflow.com/questions/4742875/change-integerproperty-to-floatproperty-of-existing-appengine-datastore
http://devblog.miumeet.com/2011/02/schedule-mapreduce-daily-on-appengine.html
http://googleappengine.blogspot.com/2009/06/10-things-you-probably-didnt-know-about.html
他多数、だが忘れた。
退職しました
Tweet
やめちゃった。
8年半前に創業直後だったこの会社に入って、ほぼ0から色々なものを作り、動かし、それなりに会社は大きくなり、立場的にもそれなりに偉くなり、給料もそれなりに貰って、居心地は悪くなくて、そうなんだけど、その一方で、自分にとって刺激は少なくなり、なんとなく楽しさが減っていた、という感じ。自分自身なんとなく安定してしまっている、という状況自体がなんとなく怖かったりもした。
それでも、暇なら色々と遊べるのだけど、日々やらないといけないことは全然減らなくて、忙しいんだけど何か刺激がない、みたいな悪い状態に陥っていた。
会社のことを考えてみても、多少老害になりそうな自分が辞めることで、新陳代謝したほうがいいのかなー、というのもある。よく知っているだけに思い切ったことができなくなっていることも多いし、なんとなく偉そうにしている自分も嫌だったりした。
前にも少し書いた ように基本的にいい会社になっているとは思うんだけど、悪い文化もできてしまっている。その悪い文化を作ることになってしまった一端は、長いこといる自分にもあるわけで、その自分が存在しつつ、その悪い文化を変えていくのはなかなか難しい、と感じているのもある。
まあ、上記すべて本音のつもりだけど微妙に嘘っぽく、後付けの理由っぽくもある。単に、色々なことに飽きてしまったのかも!
家族もいるし、常に崖っぷちを楽しむわけにもいかないけど、それなりの逃げ道を残しつつも新しいことドキドキしながらしたいとか思ってしまう。こんな時代だし、飽きちゃったとか、新しいことしたい、とかいう理由でやめちゃうのは甘っちょろいな、とも思うんだけど、まあやめちゃいました。
なんだかんだで8年半在籍したわけだけど、8年半ってのは結構長いよなあ。自分の中でも、人生で所属した組織で一番長いものになってしまった。2位が小学校で、3位が最初に入った会社かな。そういう組織を離れるのはなかなか寂しいものはあるのだけど、別れはいつか来るということで。
それにしても、この会社では本当に、大きなことから小さなことまで色々な新しい経験をさせてもらったなー。外国文化に触れることもできたし、とても人生にとってプラスになるものだったと思う。感謝でいっぱい。
で、何するの? ってことだけど、まだ内緒! ということで。もう少しロンドンにいる予定です。
Google App EngineとPythonでの素直な開発環境の構築(TDDができるように)
Tweet
追記: 続編的なものを書いた。
今年は色々なことに手を出してみよう、ってことで少し前からGoogle App Engine(以下GAE)で、あるモノを作っている。モノ自体は近いうちに公表できると思う。
基本的に、Pythonと標準っぽいフレームワークだけでやってみている。作っているものがそれなりにシンプルなのと(だからこそGAE!)、GAEでそれなりの規模の開発をするのが自分自身初めてということもあり、あまり色々なレイヤーを重ねて手こずりたくなかった、ってのがその理由。
ただ、GAE初心者なので、「いやいやそれは今時ないよ」「XXの方が100倍いい」とかあったら教えてくれると嬉しいので今のところの環境を書いておくことにした。今ならスイッチ可能。
今作っているものがJSONファイルを入出力するだけのものなので、HTML生成パートみたいのはない。
1. フレームワーク
上にも書いたように、今回は、わりと単純なアプリケーションの作成なので、Google App Engine (Python)で提供されている最小限のフレームワークだけを使っている。
2. Pythonのバージョンは2.5
Mac (Snow Leopard) だと、2.6と2.5の両方が入っていて、デフォルト(/usr/bin/python)のバージョンは2.6。そのため普通にGoogle App Engineの開発環境をインストールすると2.6が使われてしまっていて、最初の数時間無駄にした。
defaultコマンドで /usr/bin/python の指すバージョンを変えられるようだけど、他に影響を与えたくなかったので、今回は、
- コマンドラインから実行するpython関連のコマンドは2.5を付けて実行(例: easy_install-2.5)
- GoogleAppEngineLauncherで、 /usr/bin/python2.5 を指定
で、なんとかなっている。
3. ディレクトリ構成
「GAE/Pythonスタンダードなディレクトリ構成」みたいのはないようなので、いろいろな公開されているプロジェクトを参考にして以下のような配置にしてみた。最初はもうちょっと深い構成にしていたのだけど、これぐらいで十分かも。今のところModelは1ファイルにまとめてるがだいぶ増えてきたので、近い将来分離する予定。
application dir/
models.py
handlers/foo_handler.py
handlers/bar_handler.py
tests/test_foo_handler.py
tests/bar_foo_handler.py
main.py
app.yaml
controller的なものをhandlerと呼ぶみたいなのでそうしてる。
4. テスティング環境
もう当然TDDだよね、ということで最初に手をつけたのがテスト環境の構築。
まず、ここしばらくWeb開発でRuby on Rails + rspecメインな生活をしていることもあり、GAE/Python上でもそれっぽいのを探してみたところ、いくつかそれっぽいのはあったのだけど、「開発が活発ではない」「使いづらそう」という感じがしたのでその方向は諦めて、普通にUnitTestでいくことにした。マイナーなものを使うよりも、自分以外の人が触ることになったときのことを考えて、ふつーなものにしておいたほうがいいかな、という判断もある。
素のUnitTestだけだといろいろいまいちなので、以下の物を入れた。
sudo easy_install-2.5 nose
sudo easy_install-2.5 nosegae
sudo easy_install-2.5 gaetestbed
sudo easy_install-2.5 webtest
使い方等は、その中のgaetestbedの README 読めばなんとなく使い方はわかるので省略。
5. doctest併用
UnitTestの他に、コードの中にテストを書ける doctest も使っている。基本的にhandlerのテストはUnitTestで、modelのテストはdoctestで書いている。
併用している理由としては、長い間うっすらと「なんかdoctestいいなー、いつか使ってみたい」と思っていたのと、今回はそれほど複雑なmodelがないというのがある。
nosegaeは、UnitTestとdoctestの両方を同時に扱えるので、この二つを混ぜても素直に全体のテストの実行が1コマンドできる。オプションに—with-doctest を付けて、
> nosetests --with-gae --with-doctest
こんな感じ。CIでもほぼこれと同じコマンドを実行している。
6. セッション管理
GAEでは、Googleアカウントと連動したセッション+アカウント管理は標準で提供されているようだけど、今回は自前でセッション/アカウント管理をしたかったので、それは使わずに、セッション管理には gae-sessions を使ってみた。他にも色々あるのだけど、単純で軽そうだったのでこれにした。
そのライブラリの作者が作った他のライブラリとの 比較表 が参考になる。完全勝利になってるのは若干あやしいが。
このライブラリで作成したセッションに、自分で作ったユーザ用のモデルを入れるという簡単実装にした(gae-sessionsのセッションの実装自体は普通にCookieを使ったものなのでデバッグ等も簡単)。
一つ躓いた点として、gae-sessionsの導入手順をREADMEに従うと、 appengine_config.py を作ってそこでmiddlewareをセットする、となっている。ただ、ローカルの開発環境でnosegaeを使いunit testを流すと、 appengine_config.py は読んでくれないので、main.py の中で直接読み込んでごまかしてる。こんな感じ。
application = SessionMiddleware(
webapp.WSGIApplication([....],
debug=True),
cookie_key="hogehogehogehogehogehogehogehogehogehogehogehogehogehoge")
7. 本: Code in the Cloud
とりあえずGoogle App Engineを始めるにあたって何冊か読んだ本の中でそれなりに良かった奴。JavaとPython両方が書いてあるのが微妙に読みづらいが、最初に読む本、としてはそこそこいい本だと思う。テスト関係のことはほとんど載ってないけど。
8. まとめ
以上の環境で、ぼちぼちコードを書いているけど、気持ち良くTDDができている。GAE自体は思ってたよりも(話に聞いていたよりは)複雑でも難しくもなく素直だなーって印象。ドキュメントもわかりやすい。まあ、後発組は楽だなあ、と。
とは言え、もうちょっとやると壁にあたりそうなので、そのときまた何か書きたいと思う。
9. 参考にしたもの
http://www.cuberick.com/2008/11/unit-test-your-google-app-engine-models.html
https://github.com/jgeewax/gaetestbed/
http://www.slideshare.net/adeoshineye/test-driven-development-on-google-app-engine
2010年を振り返っちゃう
Tweet
全体的になかなかいい年だったかな。Web開発に関係するところを挙げてみると、
このブログを始めた
どうもヒキコモリ過ぎてるなと感じていたので、ブログを始めてみた。3日坊主にならずにそれなりに続いたのは良かった。やっぱり、自分を外に出すことは自分を客観的に見るためにも大切だ。
おかげさまで、そこそこの反応を頂けたりと、なかなか楽しいです。
技術面
Rubyを始めて2年目にしてRubyとRailsが身についた、って感じかなー。自分の学習曲線の中のいいタイミングで、 Metaprogramming Ruby という良本に出会えたのが良かった。
また、去年の後半くらいからMongoDBを追いかけていたのだけど、今年の後半、実際のシステムで少し使うことができて手応えを得ている。今後は、もうちょっと大きいところでも使ってみるつもり。MongoDBいいですよ。
スクラム初体験してみたのも今年だったかな。これは正直、完全に自分の武器になったか、と言うと微妙なところ。もうちょっと実戦で勉強。
来年
来年は、仕事面でちょっと変化の年にしたい。ここ数年、いい面でも悪い意味でも安定してしまっているので、少しかき混ぜたい感じ。
流行に乗り、スマートフォンやGoogle App Engineなどにも急激に興味が出てきたので(やらなきゃまずいかな、というのもある)、そっち方面を攻めるのが濃厚。流されやすいんです!
そんなわけで、来年もよろしくー!
RSSリーダで読む