Skip to content

Latest commit

 

History

History

criteria

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

The goal of Criteria module is to provide fluent java API (DSL) to query heterogeneous data-sources.

Features

  1. Expressive and type-safe API Compile-type validation of the query.
  2. Dynamic Combine predicates at runtime based on some logic
  3. Data-source agnostic Define criteria once and apply to different data-sources (Map, JDBC, Mongo, Elastic etc.)
  4. Blocking / asynchronous operations Generated repositories allow you to query data in blocking, non-blocking and reactive fashion

Example

Define Model

Define your model using immutables interfaces

@Value.Immutable
@Criteria // generate criteria
@Criteria.Repository // means generate repository (different from @Criteria)
interface Person {
    @Criteria.Id
    String id();
    String fullName();
    Optional<String> nickName();  
    int age();
    List<Pet> pets();
}

Query

Because of @Criteria annotation, PersonCriteria class is automatically generated by immutables. You can now leverage it to write queries similar to:

// basic query by id
PersonCriteria.person.id.in("id1", "id2", "id3");
PersonCriteria.person.id.notIn("bad_id");

// query on Strings, Comparables and Optionals
person
    .fullName.is("John") // basic equal
    .fullName.isNot("Mary") // not equal
    .fullName.endsWith("Smith") // string condition
    .fullName.is(3.1415D) // ERROR! will not compile since fullName is String (not double)
    .nickName.isPresent() // for Optional attribute
    .nickName.startsWith("Adam") // For Optional<String> attribute
    .pets.notEmpty() // condition on an Iterable
    .active.isTrue() // boolean
    .or() // disjunction (equivalent to logical OR)
    .age.atLeast(21) // comparable attribute
    .or()
    .not(p -> p.nickName.hasLength(4)) // negation on a Optional<String> attribute
    .bestFriend.value().hobby.endsWith("ing"); // chaining criterias on other entities like Optional<Friend> 

// apply specific predicate to elements of a collection
person
    .pets.none().type.is(Pet.PetType.iguana)  // no Iguanas
    .or()
    .pets.any().name.contains("fluffy"); // person has a pet which sounds like fluffy

You will notice that there are no and statements (conjunctions) that is because criteria uses Disjunctive Normal Form (in short DNF) by default.

For more complex expressions, one can still combine criterias arbitrarily using ands / ors / nots. Statement like A and (B or C) can be written as follows:

person.fullName.is("John").and(person.age.greaterThan(22).or().nickName.isPresent())

Not all entities require repository (@Criteria.Repository) but you need to add @Criteria to all classes you want to query by. For example, to filter on Person.pets.name Pet class needs to have @Criteria (otherwise PersonCriteria.pets will have a very generic Object matcher).

Use generated repository to query or update a datasource

@Criteria.Repository instructs immutables to generate repository class with find / insert / watch operations. You are required to provide a valid backend instance (mongo, elastic, inmemory etc).

MongoDatabase database = ... // prepare with CodecRegistry etc.
Backend backend = new MongoBackend(MongoSetup.of(database));

// PersonRepository is automatically generated. You need to provide only backend instance 
PersonRepository repository = new PersonRepository(backend); 

repository.insert(ImmutablePerson.builder().id("aaa").fullName("John Smith").age(22).build());

// query repository
List<Person> result = repository.find(person.fullName.contains("Smith")).fetch();

Building blocks (nomenclature)

  • Matcher Typed predicate on a particular attribute. There are several variations of the matcher and, usually, they're associated with a type (eg. StringMatcher, IterableMatcher, ComparableMatcher). Matcher internally builds an Expression.
  • Expression Abstraction of a generic expression modeled as Abstract Syntax Tree. Used internally as Intermediate Representation (IR) to transform original expression into a native query of a database. Users rarely have to deal with this API unless they write adapters for a particular backend.
  • Backend adapter to a data-source (database). Responsible for interpreting expressions and operations into native queries and API calls using vendor drivers.
  • Repository User facing API to perform queries, updates, pub/sub or other CRUD operations. Uses Backend.
  • Facet Property of repository to fine-tune its behaviour. Eg. Readable / Writable / Watchable Also one can define return types based on rxjava / async (CompletionStage) or synchronous types.

Development

common module contains runtime support. Remaining folders are backend and facet implementation.

This folder contains classes specific to Criteria API and its runtime evaluation:

  1. common shared classes by all modules
  2. elasticsearch adapter for Elastic Search
  3. mongo adapter for MongoDB based on reactive streams driver.
  4. geode adapter for Apache Geode
  5. inmemory lightweight implementation of a backend based on existing Map.
  6. rxjava rxjava repository facets.

Criteria API requires Java 8 (or later)