springredisproject-reactorspring-data-redisspring-data-redis-reactive

What is the correct way to chain redis operations in spring-data-redis-reactive?


In spring-data-redis-reactive, writing operations returns redis execution result which makes chaining operators really hard. Take the example reddit in Chapter 1 of Redis In Action. I try to re-implement like this:

@Service
public class ArticleService {

    private final ReactiveStringRedisTemplate redisTemplate;

    private final long voteScore = 432L;

    public ArticleService(ReactiveStringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public Mono<Article> createArticle(Article article) {
        long now = System.currentTimeMillis();
        Map<String, String> newArticle = new HashMap<>();
        newArticle.put("title", article.getTitle());
        newArticle.put("link", article.getLink());
        newArticle.put("poster", article.getPoster());
        newArticle.put("time", String.valueOf(now));
        newArticle.put("votes", "1");
        return redisTemplate.opsForValue()
                            .increment("article:")
                            .doOnNext(id -> redisTemplate.opsForSet().add("voted:" + id.toString(), article.getPoster()).subscribe())
                            .doOnNext(id -> redisTemplate.expire("votes:" + id.toString(), Duration.ofDays(7)).subscribe())
                            .doOnNext(id -> redisTemplate.opsForHash().putAll("article:" + id.toString(), newArticle).subscribe())
                            .doOnNext(id -> redisTemplate.opsForZSet().add("score:", "article:" + id.toString(), now + voteScore).subscribe())
                            .doOnNext(id -> redisTemplate.opsForZSet().add("time:", "article:" + id.toString(), now).subscribe())
                            .map(id -> {
                                article.setId(id);
                                article.setVotes("1");
                                return article;
                            });
    }

}

As you can see, I use doOnNext to avoid losing the id value returned by increment, and there is a subscribe() in every doOnNext to make sure every redis operation got executed. But I don't think this is the recommended way. I think applications should try to avoid subscribe() and mainly focus on chaining the flow.

What is the correct way to do many redis write operations in spring-data-redis-reactive?


Solution

  • Avoid to subscribe in between, Spring will subscribe to flow at the end if you are calling that from a WEB interface. Here is an example of how can be implemented

            return redisTemplate.opsForValue()
                                .increment("article:")
                                .flatMap(id -> // Mono.zip will execute concurrently all the modifications below
                                  Mono.zip(redisTemplate.opsForSet().add("voted:" + id.toString(), article.getPoster()),
                                      redisTemplate.expire("votes:" + id.toString(), Duration.ofDays(7)),
                                      redisTemplate.opsForHash().putAll("article:" + id.toString(), newArticle),
                                      redisTemplate.opsForZSet().add("score:", "article:" + id.toString(), now + voteScore),
                                      redisTemplate.opsForZSet().add("time:", "article:" + id.toString(), now)
                                  )
                                      .map(tuple -> id))
                                .map(id -> {
                                    article.setId(id);
                                    article.setVotes("1");
                                    return article;
                                });
    

    But you should think about to execute that modifications into a MULTI Redis command to ensure that occurs atomically https://redis.io/commands/multi. Because you don't have any validation and/or restriction the EVAL is not necessary https://redis.io/commands/eval