0

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?

leowang
  • 481
  • 5
  • 12
  • You're right, that's not the recommended/idiomatic way. The first question you need to decide on is whether you want to execute these Redis operations concurrently or sequentially. Depending on the answer you might look into operators like `flatMap` and `zip`. – Martin Tarjányi Apr 17 '20 at 14:57
  • @MartinTarjányi Could you please provide an example, both concurrently and sequentially? I've thought of using `zip`, but either zip into Tuple2, Tuple3, etc., or using `map(Tuple2::getT1)` with every `zip`? – leowang Apr 20 '20 at 03:30

1 Answers1

0

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

Javier Sainz
  • 301
  • 1
  • 2
  • 7
  • Thanks for the answer. Yes, `MULTI` is a sure thing. I'm just trying to learn the common reactive way using this extremely rare example. Anyway, `zip` is quite a handy operation. – leowang Oct 07 '20 at 06:25