-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathDefaultReviewsService.java
More file actions
119 lines (98 loc) · 4.62 KB
/
DefaultReviewsService.java
File metadata and controls
119 lines (98 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package com.example.demo.services;
import com.example.demo.generated.types.Review;
import com.example.demo.generated.types.SubmittedReview;
import com.github.javafaker.Faker;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.ConnectableFlux;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* This service emulates a data store.
* For convenience in the demo we just generate Reviews in memory, but imagine this would be backed by for example a database.
* If this was indeed backed by a database, it would be very important to avoid the N+1 problem, which means we need to use a DataLoader to call this class.
*/
@Service
public class DefaultReviewsService implements ReviewsService {
private final static Logger logger = LoggerFactory.getLogger(DefaultReviewsService.class);
private final ShowsService showsService;
private final Map<Integer, List<Review>> reviews = new ConcurrentHashMap<>();
private FluxSink<Review> reviewsStream;
private ConnectableFlux<Review> reviewsPublisher;
public DefaultReviewsService(ShowsService showsService) {
this.showsService = showsService;
}
@PostConstruct
private void createReviews() {
Faker faker = new Faker();
//For each show we generate a random set of reviews.
showsService.shows().forEach(show -> {
List<Review> generatedReviews = IntStream.range(0, faker.number().numberBetween(1, 20)).mapToObj(number -> {
LocalDateTime date = faker.date().past(300, TimeUnit.DAYS).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
return Review.newBuilder().submittedDate(OffsetDateTime.of(date, ZoneOffset.UTC)).username(faker.name().username()).starScore(faker.number().numberBetween(0, 6)).build();
}).collect(Collectors.toList());
reviews.put(show.getId(), generatedReviews);
});
Flux<Review> publisher = Flux.create(emitter -> {
reviewsStream = emitter;
});
reviewsPublisher = publisher.publish();
reviewsPublisher.connect();
}
/**
* Hopefully nobody calls this for multiple shows within a single query, that would indicate the N+1 problem!
*/
public List<Review> reviewsForShow(Integer showId) {
return reviews.get(showId);
}
/**
* This is the method we want to call when loading reviews for multiple shows.
* If this code was backed by a relational database, it would select reviews for all requested shows in a single SQL query.
*/
public Map<Integer, List<Review>> reviewsForShows(List<Integer> showIds) {
logger.info("Loading reviews for shows {}", showIds.stream().map(String::valueOf).collect(Collectors.joining(", ")));
return reviews
.entrySet()
.stream()
.filter(entry -> showIds.contains(entry.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
public void saveReview(SubmittedReview reviewInput) {
List<Review> reviewsForShow = reviews.computeIfAbsent(reviewInput.getShowId(), (key) -> new ArrayList<>());
Review review = Review.newBuilder()
.username(reviewInput.getUsername())
.starScore(reviewInput.getStarScore())
.submittedDate(OffsetDateTime.now()).build();
reviewsForShow.add(review);
reviewsStream.next(review);
logger.info("Review added {}", review);
}
public void saveReviews(List<SubmittedReview> reviewsInput) {
reviewsInput.forEach(reviewInput -> {
List<Review> reviewsForShow = reviews.computeIfAbsent(reviewInput.getShowId(), (key) -> new ArrayList<>());
Review review = Review.newBuilder()
.username(reviewInput.getUsername())
.starScore(reviewInput.getStarScore())
.submittedDate(OffsetDateTime.now()).build();
reviewsForShow.add(review);
reviewsStream.next(review);
logger.info("Review added {}", review);
});
}
public Publisher<Review> getReviewsPublisher() {
return reviewsPublisher;
}
}