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
?
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