Elasticsearch テンプレートエンジンを使ったQuery DSLプログラミングのすすめ

Kunihiko Kido
Hello! Elasticsearch.
10 min readSep 2, 2014

--

テンプレートエンジンを使って、メンテナンスしやすいフロントエンドアプリケーションを開発しよう。

ElasticsearchのQuery DSLはJSONフォーマットで、複雑なクエリも書けて便利なのですが、その反面プログラム内に記述してしまうと、全体像が把握しづらく、メンテナンスしづらいことがあります。

ElasticsearchのSearch Templateを使っても良いのですが、使用するにはElasticsearchへの事前の登録が必要だったり、Elasticsearchに関する検索クエリー以外の学習も必要なってきます。

そこでフロントエンド開発者がElasticsearchをバックエンドに検索アプリケーションを開発する際のQuery DSLプログラミング方法についてご紹介します。

その方法はタイトルにもあるようにHTMLやXMLを描画するために使用するテンプレートエンジンを使う方法です。

最近のテンプレートエンジンはHTMLやXML以外のフォーマットも出力できたり、もともとロジックとビューを分離するためのものなので、Query DSLのプログラミングにも向いています。

また、cURLコマンドで慣れ親しんだQuery DSLのフォーマットをそのまま使えるので、各種言語用のクラスとかメソッドなどを覚え直す必要がないので、学習コストも低くなります。

Query DSLを組み立てるためのテンプレートエンジンの条件

・HTML/XML以外のフォーマットを出力できること
・カスタムのヘルパーメソッドが開発し易いこと
・複雑な構文がかけないこと
・プログラミングソースと分離できること

Query DSLを組み立てるために適切なテンプレートエンジンの条件を4つ上げてみました。

1つ目の条件は当たり前ですね。今回出力しなければならないフォーマットはJSONフォーマットなので、HTML/XML以外のフィーマットも柔軟に出力できる必要があります。

2つ目の条件はパラメータで渡した値の加工をすることもあるので、カスタムのヘルパーメソッドが開発し易い方が使い易いです。

3つ目はあまり複雑な構文が書けてしまうと、結局のところメンテナンスしづらくなってしまうので、できるだけ複雑な構文が書けない方が良いです。

最後はプログラミングソースと分離して別ファイルで管理できると便利です。また、実際にQeury DSLを組み立てると構文が長くなることもあるので、ブロック単位で別ファイルにして、必要なところにインクルードできると便利です。

私はPythonで開発することが多いので、Djangoのテンプレートエンジンがおすすめですが、Python使っている人は少ないと思いますので、ちょっと調べた感じだと、PHPであればTwigやSmartyのテンプレートエンジンが良さそうです。Rubyは(x)HTMLを生成するのに特化したテンプレートが多い印象。。強いて上げればERBかな?使ったこと無いので使用する場合は上記の条件と照らし合わせてください。

最近Github Pagesで動作する検索アプリのデモを開発する際に、JavaScriptのテンプレートエンジンを調べていて、Handlebars が使い易かったので、Handlebars をベースに以降説明します。

Handlebarsとは?

http://handlebarsjs.com/

HandlebarsはJavaScript言語で使用するためテンプレートエンジンです。テンプレート内では、{{式}}と言う構文で実行されテンプレート上で書き出すことができます。scriptタグを使用してHTML内にテンプレートを定義する方法と、テンプレートをHTMLファイルと分離してコンパイルして利用する方法を提供しています。開発者は違いますが、Java用のHandlebars.javaと言うのもあるみたいですね。

JavaScriptのテンプレートエンジンは、Handlebarsに限らず事前のコンパイル機能を提供しているものが多いようです。

もともとは、ElasticsearchのJSONの検索結果を簡単に描画するのに便利なテンプレートエンジンを探していたのですが、Query DSLのプログラミングでも使い易そうでしたので紹介します。

基本的な構文

基本的には以下のように2つの中括弧で変数を囲むと参照することができます。ピリオドを使って入れ子になっている変数を参照するには次のように書きます。

{{person.name}}さん、こんにちは!

また、Block構文は#(シャープ)を頭につけて以下のように書きます。

{{#if author}}
{{firstName}} {{lastName}}
{{/if}}

Built-In Block Helpers

・The if block helper. {{#if author}}…{{/if}}
・The unless block helper. {{#unless author}}…{{/unless}}
・The each block helper. {{#each comments}}…{{/each}}
・The with Block Helper. {{#with author}}…{{/with}}

ヘルパーメソッドの登録

Handlebars.registerHelperメソッド使って、独自のヘルパーを登録することができます。

以下の例では、domain と言う名前でurlを引数で渡すと、ホスト名を返却するヘルパーを定義しています。

Handlebars.registerHelper(‘domain’, function(url) {
var hostname = $(‘<a>’).prop(‘href’, url).prop(‘hostname’);
return hostname;
});

テンプレート上で使用するには以下のようになります。

{{domain url}}

デモアプリの解説

今回Handlebarsを使って作ったデモアプリはこちら。

http://sibatokyo.github.io/demo-top-hits-aggregation/

キーワードを入力すると検索結果を表示するいわゆるサイト内検索用のアプリです。

ソースコードはGithubで公開していますので、下記のリンクからアクセスできます。

View on Github

Query DSLテンプレート

実際にHandlebarsで作成しているQuery DSLのテンプレートがGithubの下記のパスになります。

templates/query.hbs

少し内容を説明すると、2つの中括弧で囲まれた変数(式)が任意の値によって動的に書き出されます。内容はマニュアルで見かけるcurlコマンドでのbodyのリクエストの内容と同じで、動的に変更したい部分だけ変数化する感じで作成できます。

{
"query": {
"multi_match": {
"fields": [
"title.substring^3",
"description:substring",
"body.substring",
"title.yomi"
],
"query": "{{query_string}}",
"type": "most_fields"
}
},
"size": {{default top_hits_size 10}},
...
}

ヘルパーメソッド

{{query_string}}は変数の内容をそのまま出力しているだけですが、{{default ….}} はカスタムのヘルパーメッソッドです。1つ目の引数の値がセットされていなかった場合に2つ目の引数の値を書き出します。

js/handlebars.helpers.js ファイルに定義しています。内容は次のような感じです。

Handlebars.registerHelper(‘default’, function(value, default_value){
return value || default_value;
});

※ Handlebarsには、テンプレート内で他のテンプレートを呼び出す機能はありませんが、比較的簡単にヘルパーメソッドで実現できそうです。

Query DSLの生成

実際にテンプレートを指定して、Query DSLを生成している箇所は、js/jquery.siba.search.js の33行目あたりになります。

var query_dsl = Siba.Templates.query({
query_string: query_string
});

簡単ですね。必要な変数を渡すだけです。

ファイル名 query.hbs のqueryと言う名前がそのまま定義したテンプレートを呼び出すメソッド名になっています。用途毎にファイルを分けてQuery DSLのテンプレートを作成しておくと便利です。

ちなみに、Handlebarsのテンプレートをコンパイルして、Siba.Templates.<ファイル名> で呼び出せるようにGruntfile.jsに以下の内容を追加して grunt でそれらのタスクを自動化しています。

handlebars: {
compile: {
options: {
namespace: "Siba.Templates",
processName: function(filepath) {
var pieces = filepath.split("/");
return pieces[pieces.length -1].replace(/.hbs$/, '');
}
},
files: {
"js/handlebars.templates.js": ["templates/*.hbs"]
}
}
}

まとめ

Query DSLのプログラミングにテンプレートエンジンを使用することで、だいたい以下のような開発の流れになるかと思います。

1.cURLコマンドでクエリーの作成・確認
2.bodyの内容をコピーしてQuery DSLテンプレートの作成
3.テンプレートにパラメータを渡してテスト・実行

用途別に1ファイル1クエリーと言うルールでQuery DSLのテンプレートを作成しておけば、全体像も把握し易く、他の人が修正する場合やちょっとした条件の修正もメンテナンスし易くなるのではないでしょうか。何よりメインのソースから分離できるのが良いです。

--

--