foogaro / redis-annotations

A set of annotations to use within your DTOs for RedisSearch

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Redis Annotations

Framework to use in your code for RediSearch and RedisJSON. At the moment only Java is supported.

Introduction

This framework (a more humble definition would be skeleton) aims to provide a level of abstraction to leverage RediSearch and RedisJSON Redis's modules.
The concept is very easy.

Whenever you need to provide Full-Text Search capabilities in Redis, you need to load/enable the RediSearch module.
Whenever you need to manage JSON documents in Redis, you need to load/enable the RedisJSON module.
But those are just Redis server capabilities, not your application's capabilities.
Hence, redis-annotations.

HOW-TO

Before we can start let's see what we should do!

Maven

Add the following dependency in your pom.xml:

<dependency>
    <groupId>com.foogaro.util</groupId>
    <artifactId>redis-annotations</artifactId>
    <version>0.3.0</version>
</dependency>

Now you can use it in your code.

Code

To search for a JSON document, we need to index that document. The JSON document has a specific structure, so we need to decide what to index and how to manage the index.

POJO

Let's take for example a simple JSON document which describes a movie:

{
  "title" : "Rocky",
  "rating" : 5,
  "year" : 1976
}

The index needs to know which fields need to be indexed. Assuming we want to index the entire document, so that we can search for any attribute in full-text search, we will need to define a POJO representing the Movie document, as follows:

@RedisJSON(index = @Index(name = "ix-movies", indexStrategy = FTSIndexStrategy.CREATE, prefix = "movies:"))
public class Movie implements KeyValueModel {

    @Key
    private long id;
    @Text(sortable = true)
    private String title;
    @Numeric(sortable = true)
    private int rating;
    @Numeric
    private int year;

    // constructors, getters, setters and so on

}
  • @RedisJOSN defines the Movie class as JSON document;
  • @Index defines the specification of the Redis Index that will be created:
    • name sets the name of the index (i.e. "ix-movies");
    • indexStrategy manages the creation/alteration of the index, and it can be one of the following:
      • NONE - index will not be created, nor destroyed;
      • CREATE - index will be created, if it does not exist yet (like in the example above);
      • DROP - index will be dropped/deleted, nor created if it does not exist yet;
      • DROP_CREATE - index will be first dropped and then created
      • UPDATE - index will be updated with the new specification, if it already exists.
        • Not implemented yet.
    • prefix sets the prefix of the key(s) to consider for indexing (i.e. all the keys starting with "movies:").
  • @Key sets the field as the one holding the Redis key (like for "id" in the example above);
  • @Text sets a textual index to be applied to the relative field declaration (like for "title" in the example above);
  • @Numeric sets a numerical index to be applied to the relative field declaration (like for "rating" and "year" in the example above).

Now we need a way to manage our data models persistency with Redis.

Persistency

There are few annotations to enable data persistency. Assuming that we want to continue with our Movie data model, and the index specification we defined, we need to also define how to search for our Movie JSON document.
So:

  • by Title
  • by Rating
  • by Year

And this can be achieved defining an interface with those three methods, as follows:

@DataStore
public interface MovieRepository extends DataStoreOperation<Movie> {

    @Search(query = @Query("@title:({{title}})"))
    public List<Movie> byTitle(String title);
    @Search(query = @Query("@rating:({{rating}})"))
    public List<Movie> byRating(long rating);
    @Search(query = @Query("@year:({{year}})"))
    public List<Movie> byYear(int year);

}
  • @DataStore defines the interface as a DataStore (i.e. DAO, Spring Repository, and so on);
  • @Search contains query specification for the relative method that will be executed once invoked;
  • @Query defines the specification of the query that will be pushed to the Redis server to retrieve/set the data model.

The last piece of our puzzle consists of making the DAO available in the current classloader, which can be done in many different ways, depending on your application style.
Anyway, here is how you would make the DAO available:

return (MovieRepository) Proxy.newProxyInstance(
        DataStoreInvocationHandler.class.getClassLoader(),
        new Class[] { MovieRepository.class},
        new DataStoreInvocationHandler());

And you are done, you can start up your application.

Bootleggers roll your tapes

When the application starts, the FTSDetector scans for all data models marked as @RedisJSON and creates the specified indexes.
What you should see in the Redis server logs is something like the following:

9:M 30 Mar 2022 07:11:52.474 * <module> Scanning index ix-movies in background
9:M 30 Mar 2022 07:11:52.475 * <module> Scanning indexes in background: done (scanned=10)

You can now benefit from a full-text serach engine in Redis with your application or directly via the redis-cli:

curl

foogaro@MBP-di-Luigi redis-annotations-example % curl -v http://localhost:8080/api/movies/by-title/Gulp
*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /api/movies/by-title/Gulp HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Wed, 30 Mar 2022 19:35:05 GMT
<
* Connection #0 to host localhost left intact
[{"id":"movies:2","title":"Gulp","rating":2,"year":2004},{"id":"movies:4","title":"Gulp","rating":4,"year":2002},{"id":"movies:1","title":"Gulp","rating":1,"year":2005},{"id":"movies:5","title":"Gulp","rating":5,"year":2001},{"id":"movies:3","title":"Gulp","rating":3,"year":2003}]%
foogaro@MBP-di-Luigi redis-annotations-example %

redis-cli

localhost:6379> FT.SEARCH ix-movies @title:(Gulp)
 1) (integer) 5
 2) "movies:2"
 3) 1) "$"
    2) "{\"title\":\"Gulp\",\"year\":2004,\"rating\":2}"
 4) "movies:4"
 5) 1) "$"
    2) "{\"title\":\"Gulp\",\"year\":2002,\"rating\":4}"
 6) "movies:1"
 7) 1) "$"
    2) "{\"title\":\"Gulp\",\"year\":2005,\"rating\":1}"
 8) "movies:5"
 9) 1) "$"
    2) "{\"title\":\"Gulp\",\"year\":2001,\"rating\":5}"
10) "movies:3"
11) 1) "$"
    2) "{\"title\":\"Gulp\",\"year\":2003,\"rating\":3}"
localhost:6379>

You can find the example application at the redis-annotations-example repository.

About

A set of annotations to use within your DTOs for RedisSearch

License:GNU Affero General Public License v3.0


Languages

Language:Java 100.0%