読者です 読者をやめる 読者になる 読者になる

八発白中

はてなブログに引越しました。

Day 7: CL-DBI

advent-calendar/2016

これは fukamachi products advent calendar 2016 の7日目の記事です。

今日はCL-DBIについて話します。

フレームワークバトルは次のステージへ

Cavemanに一定数のファンがついた頃、Star数がRESTASに並びました。そのとき僕はうれしくて、@snmstsさんに言いました。「佐野さん!CavemanがようやくRESTASを超えたよ!」

すると佐野さんが言ったことはCommon Lispフレームワークの歴史的な一幕です。

「もうCommon Lispフレームワーク同士で争っていても仕方がない。他の言語と比較して優位性が出せなければ」

他の言語のフレームワークと比較してCavemanはどういった利点があるのか、ということを言えなければCommon Lispの導入など到底できないということです。

なんでPerlを書いてるの?

はてなで働いている頃の話。確か東京から@m2ymさんが遊びに来たときだったと思います。はてなエンジニアたちで御池通間之町を下がった「みまでり」という店に行きました。奥の階段を昇って2階の手前のテーブルに座ったことまで覚えています。

酒も2杯目くらいでほどほどに料理も食べたとき id:antipop さんがおもむろに「にとろ君、なんでCommon Lispで(仕事のコードを)書かないの?」といつもの調子で絡んできました。

いつもの調子、というのは、僕が個人的にCommon Lispが好きであり趣味のコードはCommon Lispで書いているにも関わらず仕事で平気な顔でPerlなんて書いているのか、全部Common Lispにしちゃえよ、という無責任な煽りです。

ひょっとするとCommon Lispで書くことや技術力に自信がないからじゃないの?という含みもあったかもしれません。

もちろん僕はCommon Lispが好きですが、すべてのプログラムをCommon Lispで書くべきだと思っているわけではありません。ライブラリや結合予定のアプリケーションとの都合もあります。チームで開発する場合は自分以外のメンバーのスキルセットも考慮する必要があるでしょう。やりたいことと天秤にかけて実現可能性が高いほうを選びます。Common Lispで書いてもプロジェクトが失敗すれば意味ないですからね。

当時、はてなの規模のWebサービスCommon Lispで書くのはかなりチャレンジングだったでしょう。そして実現可能性はPerlに比べて極めて低かったです。僕がそのときantipopさんに言ったマジレスの一つはDBIの欠如でした。

DBI

DBI (Database independent interface) とは使うRDBMSに依らずに統一したインターフェイスを提供するライブラリです。最近の言語では標準ライブラリとして付属していて当然ですが、Common Lispには標準ライブラリがありません。

Common Lispに当時あったのはCLSQLでした。ただこれが恐ろしく使いづらい。なんで使いづらいのかうまく説明できないのだけど、おそらくインターフェイスをリッチにしすぎているために抽象レイヤーが厚くなっていてわかりづらいのかなと思われます。いわゆるORMのような機能も入っていました。また、コードとしてもとても古くFFIとしてUFFIという今では非推奨となっているライブラリを使っています。開発も止まっておりメンテナンスも十分ではないようです。

これを改善していくよりも新しく作ったほうがよかろう。そう思ってより薄い抽象レイヤーで、DBの統一インターフェイスの提供のみを行うライブラリを作りました。それが「CL-DBI」です。

2つの統一インターフェイスの違い

統一インターフェイスという意味でCL-DBIClackと似ているかもしれません。

Clackは各Webサーバに対して統一的なインターフェイスを提供することで、同じアプリケーションをコードを書き換えることなくまったく別のWebサーバーで動かすことを可能にしました。一方でCL-DBIは使うRDBMSを自由に切り替えることができます。

しかし、作ってみてわかったことですが、CL-DBIの抽象化は不十分でした。

RDBMSは汎用的なSQLという言語を使ってデータにアクセスできるという意味で共通ですが、そのSQLRDBMSごとに微妙に違い、同じSQLではエラーとなる場合が多々あります。たとえばMySQLにはAUTO_INCREMENTがありますが、PostgreSQLの場合はSERIAL型を使い、実装もsequenceとしてテーブル定義とは別扱いになります。SQLiteはAUTOINCREMENTです。さらにインデックスはありません。

そのため、CL-DBIはClackのようにまったくコードを修正しなくても動く、というほど理想的ではないのです。

また、需要の面でも違いがありました。

Webサーバと違ってRDBMSの場合は複数種類を使うというユーザーが少ないです。MySQLファンはPostgreSQLは使わないですし、PostgreSQLファンはMySQLを使いません。そういう意味で、PostgreSQLだけ使うつもりの人はCL-DBIではなくcl-postgresやPostmodernで十分なのです。それにPostmodernは可搬性を捨てた代わりにPostgreSQL独自の機能が使えるというメリットを掲げています。

CL-DBIの有用性

それではCL-DBIは失敗だったのか。他の言語で当たり前になっている統一インターフェイスは間違ったデザインなのか。

結果から言えばそうでもありませんでした。

CL-DBIはとても薄い抽象レイヤーのため、Webアプリケーションで使おうと思ったらもう少し扱いやすくしたものを使いたいという願望が生まれます。そこでCL-DBIを前提としたツールが作られました。それがIntegralCranedataflyMitoなどです。

CL-DBIを使ったこれらの周辺ツールは、一つのRDBMSには依存しません。*1 そのためMySQLユーザもPostgreSQLユーザも共通して使うことができます。

共通のツールを使えるということはいくつもの利点があります。知識がユーザ間で共有しやすく、コミュニティを形成しやすくなります。さらに、同じライブラリの開発者も増えることが見込めます。バグ報告も増え、クオリティの向上にも有利です。

CL-DBIは僕のプロダクトの中では結構初期からあるライブラリですが、年を減るごとに重要性は増しています。

おわりに

CL-DBIGitHubで公開されており、現在Starは74です。

明日のアドベントカレンダーは8日目のShellyについてです。お楽しみに。

*1:IntegralはSQLの抽象化ができていないためPostgreSQLに対応していません。これは後継のMitoにより解決されています。

Day 6: circular-streams

advent-calendar/2016

これは fukamachi products advent calendar 2016 の6日目の記事です。

今日はcircular-streamsについて話します。

circular-streamsとは

これまでのプロダクトとは違ってcircular-streamsは裏側で使われているライブラリなので知らない人も多いと思います。

Common Lispのストリームはreadを始めて一方向に読み込みを進めます。一度readしたものは基本的に戻すことはできません。ストリームの終端まで来るとEOFになります。

この性質は他の言語でも一般的なものだと思われます。けれど、複数のコードから一つのストリームを共有するような場合に問題になります。なぜなら先に走ったコードがストリームを読み終わってしまうと、もう一方がストリームを読みに行ってもEOFが帰ってきて読み込めないからです。

これはClackのアーキテクチャにとっては不利な仕様です。

複数のミドルウェアでリクエストボディのストリームを共有していると、前のミドルウェアが全部ストリームを読んでしまった場合にbody-parametersを読めなくなってしまいます。

circular-streamsはこの問題を解決します。

make-circular-streamsでストリームをラップすると、EOFまで読んだあとにまた再び最初からreadすることができます。内部的にバッファを持っており、一度読んだものをまるでストリームから読んだかのように見せます。

(defparameter *stream*
              (flex:make-in-memory-input-stream
               #(72 101 108 108 111)))

(defparameter *circular-stream*
              (make-circular-stream *stream*))

(read-char *circular-stream*)          ;=> #\H
(read-char *circular-stream*)          ;=> #\e
(read-char *circular-stream*)          ;=> #\l
(read-char *circular-stream*)          ;=> #\l
(read-char *circular-stream*)          ;=> #\o
(read-char *circular-stream* nil :eof) ;=> :eof
(read-char *circular-stream*)          ;=> #\H

面白いでしょう?

おわりに

circular-streamsはGitHubで公開されており、現在Starはたったの2です。寂しい。

明日のアドベントカレンダーは7日目のcl-dbiについてです。お楽しみに。

サムライト株式会社を退職します

今年末12月31日でサムライト株式会社を退職することにしました。

2年間という短い期間ではありましたが、Common Lispでの開発を存分にやらせていただきました。おかげで高速なWebサーバーのWooの運用実績ができ、Common Lispが実用に足るだけでなく他の言語を凌ぐパフォーマンスを出せるということが実証できたと思います。

次の雇用先は決まっていないのでこれからのんびり探す予定です。

もし雇ってもいいという企業がいらっしゃれば、退職と雇用先の募集についてをご覧ください。

Day 5: ningle

advent-calendar/2016

これは fukamachi products advent calendar 2016 の5日目の記事です。

今日はningleについて話します。

Cavemanをもっと小さく

CavemanはマイクロWebフレームワークではありましたが、一般的なものと違ってプロジェクト雛形生成機能がついていました。

PerlのAmon2から影響されて作られたので当然と言えば当然なのですが、RubySinatraPythonのFlaskのようにまずは1ファイルでさらっと書いて起動できるといった手軽さに比べると鈍臭く感じてしまいます。

これはCavemanが悪いわけではありません。他のフレームワークがURLディスパッチャしか提供していないのに対し、Cavemanはテンプレートエンジンとの連携や環境による設定の切り替えなども含まれ、プロジェクトのディレクトリ構造がある程度固定されるのです。

けれども仮に、そこを敢えて1ファイルだけで完結できるようなフレームワークにするならどのようなものだろうと思いつき、それを作ってみることにしました。

そのフレームワークが「ningle」です。

名前の由来はアイヌ伝承の小人「ニングル」です。Cavemanが無骨な原始人とすれば、ningleは可愛げのある小人だというわけです。

アノテーションではなくsetf

3日目のCavemanの記事で、アノテーションによるルーティングルール定義について話しました。一方でningleはsetfを使った定義をします。

(defvar *app* (make-instance 'ningle:<app>))

(setf (ningle:route *app* "/")
      "Welcome to ningle!")

(setf (ningle:route *app* "/login" :method :POST)
      #'(lambda (params)
          (if (authorize (cdr (assoc "username" params :test #'string=))
                         (cdr (assoc "password" params :test #'string=)))
              "Authorized!"
              "Failed...Try again.")))

(clack:clackup *app*)

Common Lispsetfは代入の意味ではありますが、その動作は自分で定義することができます。setf ningle:routeはappのルーティングルールに追加するという挙動です。

なぜ今回はアノテーションを使わなかったのか。それはningleが小さいだけでなく単純でなければならないという設計思想が所以でした。

新しいアノテーションやマクロはユーザーに使い方を覚えてもらう必要があります。一方でsetfはCommon Lispの標準の機能であり、親しみやすく、学習コストも下がります。

Lispのマクロは強力です。マクロがあるからLispを使うといっても過言ではありません。けれど、それはむやみに使えばいいというものではありません。学習コストと天秤にかけて、利便性が勝るときだけ使うから光るのです。

さらにningleでは、Clackにある機能は隠蔽せずにそのまま使うことにしました。このためningleではClackの使い方を知っていることを前提としてはいますが、その分Clackに親しんだ人には扱いやすく感じると思います。

これらの違いがningleを機能の少ないCavemanではなく、シンプルで取り回しの効くマイクロWebフレームワークにしたと自負していたのですが、誤算もありました。

Caveman vs ningle

ningleをリリースしてからとても多くの人にGitHubやメールなどで同じ質問を受けました。

「Cavemanとningleの2つあるようだけど、違いは何?どちらもマイクロWebフレームワークと書いてある。どちらを使うべきだろう?」

好きな方を使えばいいだけなのですが、選択肢が多いと不安になる気持ちもわかります。

同じ質問に答えるのに飽き飽きしていた僕は、そこでCavemanをより進化させ、完全にningleとの差別化を図りました。そういった経緯で作られたCaveman2はRDBMSとの連携が標準対応しており、一般的なWebアプリケーションで必要な最小限のものは揃えてあります。

また、Caveman2はなんとningleに依存しています。内部のURLディスパッチャ部分をningleを使うことで、階層的なプロダクトであることが明示できるとともに、Clackの思想である再利用性を実現しています。まあ、似たコードを2つメンテしたくないっていうのもあったんですけど。

それからのWebアプリケーションはCaveman2で作るほうが一般的になり、ningleはアプリケーション内に埋め込むような小さなHTTPアプリケーションとしての用途で使われることが多くなりました *1

おわりに

ningleはGitHubで公開されており、現在Starは110です。

明日のアドベントカレンダーは6日目のcircular-streamsについてです。お楽しみに。

*1:たとえばQlotのQuicklisp dist配布用のモックサーバーなど

Day 4: cl-project

advent-calendar/2016

これは fukamachi products advent calendar 2016 の4日目の記事です。

今日はcl-projectについて話します。

コーディングスタイルの提唱

前回の記事のCavemanからは話が前後します。

Clackの思想は疎結合性と再利用性を高めることでした。それはClackを使ったアプリケーションだけでなくClack自身についても同様であり、つまりは一般的なCommon Lispアプリケーション自体の疎結合性を高めることも目指したものでした。

Common Lispにはアプリケーションの分割単位として「パッケージ」と「システム」があります。「システム」はプロジェクトの一つの読み込み単位であり、複数のファイルと複数のパッケージを含みます。

他のプログラム言語に親しんだ人には違和感があるかもしれませんが、Common Lispのパッケージはファイル単位と結びついていません。つまり、1ファイルに複数のパッケージを書くことができますし、1つのパッケージを複数のファイルで共有することもできます。

従来のCommon Lispアプリケーションで一般的だったのは、package.lispというファイルにdefpackage (パッケージ定義文) をまとめてしまい、それぞれのファイルでは一つのパッケージを共有するものでした。

このスタイルにはいくつもの問題があります。

  • ファイルに分割されていてもシンボルの衝突が起こる
  • 依存ライブラリが実際にどのファイルで使われているかわかりづらい
  • defpackage宣言が同一ファイルにないため管理がおざなりになりがち

特に依存ライブラリやファイルの依存関係がわかりづらくなることが大きな問題です。

そこでClackでは1ファイルにつき1パッケージのスタイルを提唱しました。そして:useの使用を極力やめ、必要なシンボルを:import-fromで列挙することにより依存しているシンボルを一覧できるようにしました。これらはAriel LabsのCL Style Guideで詳しく書いています。

提唱から布教へ

その頃僕はいくつものCommon Lispライブラリを並列で作り始めていました。Common Lispライブラリを作り始めるときに.asdファイルを作ったりREADMEを置いたりテストファイルを配置したりなど、何度も同じことをしなければなりません。

そこでこれをテンプレート化してライブラリの雛形を生成できるライブラリを作りました。それが「cl-project」です。

生成される雛形のプロジェクトは当然ながら僕が推奨するスタイル、Clackスタイルのプロジェクト構成でした。ファイル毎にパッケージが定義され、READMEがあり、テスト用のファイルも同時に生成されます。

競合としてQuicklispのZackが作ったquickproject というものもあります。こちらを愛用している方もいると思いますが、違いとしては1パッケージ1ファイルのスタイルではないことと、テストの雛形は生成されないことです。

この地味な名前のプロダクトは実は僕のプロダクトの中ではかなりよく使われている部類です。

普及のためという戦略的な意図はなかったにせよ、結果的にClackスタイルの布教にも貢献をしました。現在では新しいプロダクトの多くは1パッケージ1ファイルのスタイルで書かれ、Proveが使われたプロジェクトも多いです。

おわりに

cl-projectはGitHubで公開されており、現在Starは69です。

明日のアドベントカレンダーは5日目のningleです。お楽しみに。