elastic4s

repository·series/9.x·Indexed 23 days ago

https://github.com/philippus/elastic4s

A type-safe, idiomatic Scala client for Elasticsearch featuring a powerful DSL for programmatic request construction. It provides seamless integration with Scala's asynchronous workflows via Futures and supports various effect libraries including Cats Effect IO, ZIO Task, Monix Task, and Scalaz Task. Key features include JSON typeclass support for Jackson, Circe, Json4s, PlayJson, and Spray Json, a reactive-streams implementation, and a dedicated testkit for testing Elasticsearch interactions.

Tokens
21.4K
Snippets
88
Records
122
Agent score
82%

What's inside elastic4s

  1. Overview of elastic4s

    series/9.x

    elastic4s is a concise, idiomatic, reactive, and type-safe Scala client for Elasticsearch. It provides a DSL that allows for programmatic request construction with compile-time error checking.

    Key features include:

    • Type-safe DSL: Requests are built using a builder-like pattern similar to the Java or REST APIs, but optimized for Scala.
    • Asynchronous Integration: Uses standard Scala Futures to integrate into asynchronous workflows.
    • Scala Idioms: Uses Scala collections instead of Java collections, returns Option instead of null, and uses scala.concurrent.duration.Duration for time values.
    • JSON Typeclass Support: Supports automatic indexing, updating, and searching of domain classes via typeclasses for Jackson, Circe, Json4s, PlayJson, and Spray Json.
    • HTTP Client Support: Compatible with both Java and Scala HTTP clients, such as Akka-Http.
    • Reactive Streams: Provides a reactive-streams implementation.
    • Testkit: Includes a testkit subproject designed for testing Elasticsearch interactions.
  2. How effect types work in elastic4s

    series/9.x
    Internally, elastic4s uses the cats.Functor typeclass to map effects. This allows the library to remain agnostic of the specific asynchronous execution model (Future, Task, etc.) used by the application, as long as an implicit Functor instance for that type is available in the scope.
  3. Use Elastic Reactive Streams for non-blocking data flow

    series/9.x

    For non-blocking streaming of data, elastic4s provides a Reactive Streams implementation built using Akka. To use this, you must add the elastic4s-streams module dependency.

    This implementation supports two primary patterns:

    1. Elastic Subscriber: Stream data from a publisher into Elasticsearch.
    2. Elastic Publisher: Stream documents from Elasticsearch out to subscribers.
  4. Filter cluster-level API results by node

    series/9.x
    Many cluster-level APIs (such as Task Management, Nodes Stats, and Nodes Info) allow you to operate on a subset of nodes using node filters. This prevents the API from reporting results from every node in the cluster, allowing for more targeted monitoring and debugging.
  5. How to create an ElasticClient

    series/9.x

    The entry point for elastic4s is an instance of ElasticClient. You create it by passing an HTTP library implementation (like JavaClient) to the ElasticClient companion object.

    JavaClient is configured using ElasticProperties, which accepts a single string containing the protocol, host, and port. For multiple nodes, provide a comma-separated list of endpoints.

    // Single node
    val props = ElasticProperties("http://host1:9200")
    val client = ElasticClient(JavaClient(props))
    
    // Multiple nodes
    val nodes = ElasticProperties("http://host1:9200,host2:9200,host3:9200")
    val client = ElasticClient(JavaClient(nodes))
    val props = ElasticProperties("http://host1:9200")
    val client = ElasticClient(JavaClient(props))
  6. How the Suggestions API works

    series/9.x

    The Suggestions API returns similar-looking terms (suggestions) for a given text using a suggestor. Suggestions are included in a search query by adding a suggestions block.

    Key concepts:

    • Suggestion Types: You can use term, fuzzyCompletion, completion, or phrase suggestion types.
    • Naming: Each suggestion must have a unique name. This name is used to identify the suggestion in the response.
    • Retrieval: The preferred way to access results is to use the original suggestion object (e.g., the object created via termSuggestion) to look up the response from the search result. This avoids manual casting.
    • Entries and Options: A suggestion response contains entries. Each entry corresponds to a term in the input text. The options array within an entry contains the actual suggested terms.
  7. Index domain models using the Indexable typeclass

    series/9.x

    To avoid manually creating maps of fields, you can use the Indexable[T] typeclass to index domain models directly. By providing an implicit Indexable[T] instance for a class T, you can pass an instance of T to the .doc() method on an IndexRequest.

    Elastic4s provides automatic derivation for common Scala JSON libraries. To use them, add the corresponding elastic4s module to your project and bring the implicits into scope.

    // a simple example of a domain model
    case class Character(name: String, location: String)
    
    // turn instances of characters into json
    implicit object CharacterIndexable extends Indexable[Character] {
      override def json(t: Character): String = s""" { "name" : "${t.name}", "location" : "${t.location}" } """ 
    }
    
    // now index requests can directly use characters as docs
    val jonsnow = Character("jon snow", "the wall")
    client.execute {
      indexInto("gameofthrones").doc(jonsnow)
    }
  8. How ElasticClient and HttpClient work together

    series/9.x

    The primary entry point for executing requests in elastic4s is the ElasticClient class. It is responsible for executing requests (like SearchRequest) and returning responses (like SearchResponse).

    ElasticClient does not handle HTTP directly; instead, it delegates HTTP functions to an implementation of the HttpClient typeclass. This allows you to swap the underlying transport layer (e.g., Akka HTTP, STTP, or the official Java client) while keeping the same high-level ElasticClient API.

  9. Iterate over search results with SearchIterator

    series/9.x

    The SearchIterator provides a simple way to iterate over all results in a search by implementing scala.collection.Iterator. It handles the complexity of re-requesting data via Elasticsearch scrolls automatically.

    Key characteristics:

    • It is a blocking implementation: it will block between requests to fetch the next batch.
    • The search request must specify a keepAlive value for scrolling.
    • It can return marshalled domain objects (using an implicit HitReader) or raw Elasticsearch Hit objects.

    If you require a non-blocking solution, use the Reactive Streams implementation instead.

    implicit val reader : HitReader[MyType] =  ...
    val iterator = SearchIterator.iterate[MyType](client, search(index).matchAllQuery.keepAlive("1m").size(50))
    iterator.foreach(println)
  10. Control document visibility using RefreshPolicy

    series/9.x

    When indexing documents, you can use RefreshPolicy to control when newly indexed documents become available for search via the Search API. Note that this affects visibility, not data consistency or durability.

    Available policies:

    • RefreshPolicy.IMMEDIATE: Forces a refresh immediately after the index operation. The request blocks until the refresh is complete and documents are searchable. Warning: Using this under heavy load can cause contention by forcing Elasticsearch to refresh too frequently.
    • RefreshPolicy.WAIT_FOR: The request blocks until the next scheduled refresh occurs. Documents become searchable after that refresh.
    • RefreshPolicy.None: The request returns as soon as Elasticsearch acknowledges it. Documents only become searchable after the next scheduled refresh.
  11. Convert search hits using the HitReader typeclass

    series/9.x

    Elastic4s uses the HitReader[T] typeclass to marshal SearchHit instances back into domain types. By providing an implicit HitReader[T] in scope, you can use .to[T] or .safeTo[T] on the search response.

    • .to[T]: Returns successful conversions, dropping any errors.
    • .safeTo[T]: Returns a sequence of Either[Throwable, T] to preserve error information.

    If using the Jackson implementation, special fields like _timestamp, _id, _type, _index, or _version are automatically populated if they exist in your domain object.

    case class Character(name: String, location: String)
    
    implicit object CharacterHitReader extends HitReader[Character] {
      override def read(hit: Hit): Either[Throwable, Character] = {
        val source = hit.sourceAsMap
        Right(Character(source("name").toString, source("location").toString))
      }
    }
    
    val resp = client.execute {
      search("gameofthrones").query("kings landing")
    }.await // don't block in real code
    
    // .to[Character] will look for an implicit HitReader[Character] in scope
    // and then convert all the hits into Characters for us.
    val characters: Seq[Character] = resp.result.to[Character]