データベースは便利なものですが、クエリ処理は分割や汎用化が難しく、ORMを素朴に使うだけでは地獄を呼ぶ事もあります。
そしてScalaとSlickがそれを救うというお話です。
名前と職業を属性に持つPersonテーブルへの検索を例に考えます。
古のJDBCプログラミング風味
val query = new StringBuilder("select * from person where ")
if (なまえをあいまいけんさく) {
query.append(" name like '%さん'")
} else {
query.append(" name = 'はなこさん'")
}
query.append(" and ")
if (職業は複数ある) {
query.append(" job in ('忍者','犬')")
} else {
query.append(" job = '侍'")
}
invokeQuery(query)
この方法の場合、SQL文法に関する配慮(この後にはandが要るぞ!あ!カンマで区切らないと、、あ!エラーでこけたぞ!みたいなヤツ)が必要で、if文が多くなり複雑な処理になりがちです。
SQL文字列をプログラムで組み立てるのに疲れた頃にORMが台頭してきましたが、静的なクエリを必要な数だけ多数書くタイプのモノが多かったように思います。
動的に文字列クエリを作る事に皆疲れていたのでしょうが、少しずつ違うクエリを何本も書く事も同じぐらい寂しいものです。
ScalaにはSlickという良いものがあり、SQL文法への配慮が不要でクエリっぽいものを記述する事ができます。
ベタなSlick風味
Persons.where { p =>
if (なまえをあいまいけんさく) {
p.name like "%さん"
} else {
p.name === "はなこさん"
}
}.where { p =>
if (職業は複数ある) {
p.job inSet List("忍者", "犬")
} else {
p.job === "侍"
}
}.list
静的に記述できるので嬉しいですが、この路線でもやりたい事が複雑になると手に負えなくなる気配がします。
プログラマであれば複雑なものは処理を分けて書きたいと思いますよね。やってみましょう。
"Composable"なSlick
名前を扱う関数オブジェクトの生成
val nameWhereSection = (p: Persons.type) => if (なまえをあいまいけんさく) {
p.name like "%さん"
} else {
p.name === "はなこさん"
}
職業を扱う関数オブジェクトの生成
val jobWhereSection = (p: Persons.type) => if (職業は複数ある) {
p.job inSet List("忍者", "犬")
} else {
p.job === "侍"
}
リストに関数オブジェクトを入れて、クエリにfoldで適用する
List(nameWhereSection, jobWhereSection).foldLeft(personsQuery)((q, w) => q.where(w)).list
slickはQueryオブジェクトのメソッドに関数オブジェクトを渡すことで処理を行うので、関数オブジェクトの生成と処理の実行を完全に分けることが可能です。
Query#whereの引数はこのような型を持ちますのでこれを実装するわけです。
(Persons.type)=>(Column[Boolean])
このような「クエリの一部分」を表した小さく再利用しやすい関数オブジェクトを複数組み合わせる事で大きなクエリ処理を実現することがSlickでは可能です。
つまり"Composable"なクエリ処理の実現です!!!
この方法は、オブジェクト指向による抽象化と相性が良いので複雑な設計も破綻する事がなく、データベースと仲良くする事ができます。