19 April 2010 Engineering

ElasticSearch Just Got Groovy

Von Shay Banon

Just pushed into master (upcoming 0.7 release) a Groovy client wrapper on-top of the Java API elasticsearch provides.

Using elasticsearch with dynamic languages makes a lot of sense, especially thanks to its domain driven approach, and thanks to the fact that Groovy runs on the JVM, it can make use of the native elasticsearch Java API. Here are some examples:

Creating a node (that acts as a client) within the cluster is simple using the GNodeBuilder:

<span class="kwd">def</span><span class="pln"> nodeBuilder </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> org</span><span class="pun">.</span><span class="pln">elasticsearch</span><span class="pun">.</span><span class="pln">groovy</span><span class="pun">.</span><span class="pln">node</span><span class="pun">.</span><span class="typ">GNodeBuilder</span><span class="pun">()</span><span class="pln">
nodeBuilder</span><span class="pun">.</span><span class="pln">settings </span><span class="pun">{</span><span class="pln">
    node </span><span class="pun">{</span><span class="pln">
        client </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> gNode </span><span class="pun">=</span><span class="pln"> nodeBuilder</span><span class="pun">.</span><span class="pln">node</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> client </span><span class="pun">=</span><span class="pln"> gNode</span><span class="pun">.</span><span class="pln">client</span>

Note, right from the start, the domain driven settings applied. Settings in elasticsearch can be defined using JSON, and, by utilizing Grails JsonBuilder, they can be expressed as a Groovy Closure.

Next, lets index some data:

<span class="kwd">def</span><span class="pln"> future </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">index </span><span class="pun">{</span><span class="pln">
    index </span><span class="str">"twitter"</span><span class="pln">
    type </span><span class="str">"tweet"</span><span class="pln">
    id </span><span class="str">"1"</span><span class="pln">
    source </span><span class="pun">{</span><span class="pln">
        user </span><span class="pun">=</span><span class="pln"> </span><span class="str">"kimchy"</span><span class="pln">
        message </span><span class="pun">=</span><span class="pln"> </span><span class="str">"elasticsearch is groovy"</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// a listener can be added to the future</span><span class="pln">
future</span><span class="pun">.</span><span class="pln">successs </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="typ">IndexResponse</span><span class="pln"> response </span><span class="pun">-></span><span class="pln">
    println </span><span class="str">"Indexed $response.index/$response.type/$response.id"</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// or, we can wait for the response</span><span class="pln">
println </span><span class="str">"Indexed $future.response.index/$future.response.type/$future.response.id"</span>

Here, we indexed a tweet into an index called twitter, the type is a tweet and under id 1. Note that the indexed JSON is expressed using the same JsonBuilder.

Also, all operations in elasticsearch are asynchronous allowing to either register a listener (on success/failure) or work with an ActionFuture. The future in the Groovy case is an enhanced Groovy future called GActionFuture. It allows to wait for the response, or register Closure that will be called on a successful index, failed index, or both.

All APIs are used exactly the same as the above index one. Let me finish with an example of the Search API, which shows the power of the search query DSL:

<span class="kwd">def</span><span class="pln"> search </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">search </span><span class="pun">{</span><span class="pln">
    indices </span><span class="str">"twitter"</span><span class="pln">
    types </span><span class="str">"tweet"</span><span class="pln">
    source </span><span class="pun">{</span><span class="pln">
        query </span><span class="pun">{</span><span class="pln">
            term</span><span class="pun">(</span><span class="pln">user</span><span class="pun">:</span><span class="pln"> </span><span class="str">"kimchy"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

println </span><span class="str">"Search returned $search.response.hits.totalHits total hits"</span><span class="pln">
println </span><span class="str">"First hit tweet message is $search.response.hits[0].source.message"</span>

As you can see, using elasticsearch from Groovy is groovy ;). Someone up for building a grails plugin utilizing this?

-shay.banon