Skip to content

secretworry/elaxto

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Elaxto

The real DSL for ElasticSearch in Elixir

Why Elaxto

Since ElasticSearch has defined a DSL using JSON, why bother writing another lib to write a DSL for another DSL? The answer is JSON is too verbose. Especially, the map expression in Elixir makes the situation worse(%{} instead of {}, => instead of :).

Considering a ElasticSearch query is just a series of nested function calls, why don't we just use function calls in the Elixir to emulate the ES Query DSL?

Here is a quick compare using a bool query example from ES Official site

Firstly, our function-based DSL

query(
  bool(
    must: term(user: "kimchy"),
    filter: term(tag: "tech"),
    must_not: range(age: [from: 10, to: 20]),
    should: [term(tag: "wow"), term(tag: "elasticsearch")],
    minimum_should_match: 1,
    boost: 1.0
  )
)

Here the original query expression

{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user" : "kimchy" }
      },
      "filter": {
        "term" : { "tag" : "tech" }
      },
      "must_not" : {
        "range" : {
          "age" : { "from" : 10, "to" : 20 }
        }
      },
      "should" : [
        { "term" : { "tag" : "wow" } },
        { "term" : { "tag" : "elasticsearch" } }
      ],
      "minimum_should_match" : 1,
      "boost" : 1.0
    }
  }
}

and the Elixir version

%{"query"=> %{
    "bool" => %{
      "must" => %{
        "term" => %{ "user" => "kimchy" }
      },
      "filter"=> %{
        "term" => %{ "tag" => "tech" }
      },
      "must_not" => %{
        "range" => %{
          "age" => %{ "from" => 10, "to" => 20 }
        }
      },
      "should" => [
        %{ "term" => %{ "tag" => "wow" } },
        %{ "term" => %{ "tag" => "elasticsearch" } }
      ],
      "minimum_should_match" => 1,
      "boost" => 1.0
    }
  }
}

I think the result is clear enough, our solution wins out with more concise expression, and less LOC.

Besides, we provided a simple ElasticSearch abstraction helping you to organize your ElasticSearch related code.

Installation

Still under construction, wants to be an early bird?

Add elaxto to your list of dependencies in mix.exs:

  ```elixir
  def deps do
    [{:elaxto, github: "secretworry/elaxto", branch: :master}]
  end
  ```

Usage

Building Query

Just import Elaxto.Query in your module, you can use the macro query/1 and suggest/1 to build ElasticSearch query.

defmodule MyApp.UserIndex do
  import Elaxto.Query
  def search_by_user_name(user_name) do
    MyApp.Elacto.post({:my_app, :user},
      query(
        term(user_name: ^user_name) # Don't forget to use the escape character ^ here
      )
    )
  end
end

Now you can defining ES Query like invoking a bunch of elixir calls, but here's some hint.

  1. Since it's still a elixir call, you can only put the keyword-like args at the tail of args To construct a query like this javascript {"query": { "bool": {"boot": 1.0, "must": { "term": {"user": "name"}}}}} instead of adding the boot args at the front of the bool call, you have to push it to the end of argument list, like this elixir query(bool(must: term(user: "name"), boot: 1.0)) # instead of # query(boot: 1.0, bool(must: term(user: "name")))
  2. Use ^ to escape variables in the query elixir name = "Alice" query(term(user_name: ^name))
  3. Be conscious about how the queries should be composed. We can compose two queries in two ways, the first is to let them merge into a list, the second would be composing them into a map.
```elixir
# We archive the first one through wrapping the two query into an array
bool(
  should: [term(tag: "elaxto"), term(tag: "woo")]
)
# generates: {should: [ {term: {tag: "elaxto"}, {term: {tag: "woo"}]}

# We archive the second on through wrapping the two query into an double-array `[[` and `]]
bool(
  must: [[term(tag: "elaxto", match(message: "awesome")]]
)
# generates: {must: {term: {tag: "elaxto"}, match: {message: "awesome"}}}
```

Send queries to the server

Code for this section is still under frequently reconstruction

  1. Define your own Elaxto.Http.Adapter or use the provided Elaxto.Http.Adapters.Maxwell(thanks to Maxwell a awesome http client adapter)
  2. Define a Elaxto in your project elixir defmodule MyApp.Elaxto do use Elaxto, otp_app: :my_app end
  3. Add configuration for the Elaxto in your config file elixir config :my_app, MyApp.Elaxto, http_adapter: Elaxto.Http.Adapters.Maxwell you can also pass options to the adapter in the config elixir config :my_app, MyApp.Elaxto, http_adapter: {Elaxto.Http.Adapters.Maxwell, [key: value]}
The available options are

* `http_adapter` - (required) the Http Adapter used for the Elaxto, you can config different adapter for different env
* `host`         - (optional) the host & port for ElasticSearch server, defaults to `http://localhost:9200`
  1. Now you can use get/1, post/2, put/2, delete/1 to interact with the server

Defining index schema

An index schema defines how to create an index, and how to convert models into documents of ES

(To Be Continued)

Releases

No releases published

Packages

No packages published

Languages