Merge pull request #2211 from eugenp/bael-951-responsive-services
bael-951-responsive-services
This commit is contained in:
commit
026c9717ce
@ -46,6 +46,15 @@
|
|||||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-hystrix</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>h2</artifactId>
|
||||||
|
@ -1,21 +1,32 @@
|
|||||||
package com.baeldung.spring.cloud.bootstrap.svcrating;
|
package com.baeldung.spring.cloud.bootstrap.svcrating;
|
||||||
|
|
||||||
import com.netflix.appinfo.InstanceInfo;
|
|
||||||
import com.netflix.discovery.EurekaClient;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
|
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
|
||||||
|
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
|
||||||
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
|
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
|
||||||
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
|
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
|
||||||
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
|
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
|
||||||
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
|
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
|
||||||
|
import org.springframework.context.annotation.AdviceMode;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
import com.netflix.appinfo.InstanceInfo;
|
||||||
|
import com.netflix.discovery.EurekaClient;
|
||||||
|
import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect;
|
||||||
|
|
||||||
import zipkin.Span;
|
import zipkin.Span;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableEurekaClient
|
@EnableEurekaClient
|
||||||
|
@EnableHystrix
|
||||||
|
@EnableTransactionManagement(order=Ordered.LOWEST_PRECEDENCE, mode=AdviceMode.ASPECTJ)
|
||||||
public class RatingServiceApplication {
|
public class RatingServiceApplication {
|
||||||
@Autowired
|
@Autowired
|
||||||
private EurekaClient eurekaClient;
|
private EurekaClient eurekaClient;
|
||||||
@ -48,4 +59,11 @@ public class RatingServiceApplication {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
@Order(value=Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public HystrixCommandAspect hystrixAspect() {
|
||||||
|
return new HystrixCommandAspect();
|
||||||
|
}
|
||||||
}
|
}
|
@ -20,17 +20,20 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
http.httpBasic()
|
http
|
||||||
.disable()
|
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.regexMatchers("^/ratings\\?bookId.*$").authenticated()
|
.regexMatchers("^/ratings\\?bookId.*$").authenticated()
|
||||||
.antMatchers(HttpMethod.POST,"/ratings").authenticated()
|
.antMatchers(HttpMethod.POST,"/ratings").authenticated()
|
||||||
.antMatchers(HttpMethod.PATCH,"/ratings/*").hasRole("ADMIN")
|
.antMatchers(HttpMethod.PATCH,"/ratings/*").hasRole("ADMIN")
|
||||||
.antMatchers(HttpMethod.DELETE,"/ratings/*").hasRole("ADMIN")
|
.antMatchers(HttpMethod.DELETE,"/ratings/*").hasRole("ADMIN")
|
||||||
.antMatchers(HttpMethod.GET,"/ratings").hasRole("ADMIN")
|
.antMatchers(HttpMethod.GET,"/ratings").hasRole("ADMIN")
|
||||||
|
.antMatchers(HttpMethod.GET,"/hystrix").authenticated()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
.and()
|
.and()
|
||||||
|
.httpBasic().and()
|
||||||
.csrf()
|
.csrf()
|
||||||
.disable();
|
.disable();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,28 @@
|
|||||||
package com.baeldung.spring.cloud.bootstrap.svcrating;
|
package com.baeldung.spring.cloud.bootstrap.svcrating;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||||
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
|
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession
|
@EnableRedisHttpSession
|
||||||
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
|
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
|
||||||
|
@Autowired
|
||||||
|
Environment properties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
public JedisConnectionFactory connectionFactory() {
|
||||||
|
JedisConnectionFactory factory = new JedisConnectionFactory();
|
||||||
|
factory.setHostName(properties.getProperty("spring.redis.host","localhost"));
|
||||||
|
factory.setPort(properties.getProperty("spring.redis.port", Integer.TYPE,6379));
|
||||||
|
factory.afterPropertiesSet();
|
||||||
|
factory.setUsePool(true);
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,34 @@
|
|||||||
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
|
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.GeneratedValue;
|
import javax.persistence.GeneratedValue;
|
||||||
import javax.persistence.GenerationType;
|
import javax.persistence.GenerationType;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Transient;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class Rating {
|
public class Rating implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 3308900941650386473L;
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
private Long bookId;
|
private Long bookId;
|
||||||
private int stars;
|
private int stars;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private boolean fromCache;
|
||||||
|
@Transient
|
||||||
|
private Long cachedTS = -1L;
|
||||||
|
|
||||||
public Rating() {
|
public Rating() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,4 +66,21 @@ public class Rating {
|
|||||||
public void setStars(int stars) {
|
public void setStars(int stars) {
|
||||||
this.stars = stars;
|
this.stars = stars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFromCache() {
|
||||||
|
return fromCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFromCache(boolean fromCache) {
|
||||||
|
this.fromCache = fromCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCachedTS() {
|
||||||
|
return cachedTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCachedTS(Long cachedTS) {
|
||||||
|
this.cachedTS = cachedTS;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,118 @@
|
|||||||
|
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.SetOperations;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.ValueOperations;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public class RatingCacheRepository implements InitializingBean {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JedisConnectionFactory cacheConnectionFactory;
|
||||||
|
|
||||||
|
private StringRedisTemplate redisTemplate;
|
||||||
|
private ValueOperations<String, String> valueOps;
|
||||||
|
private SetOperations<String, String> setOps;
|
||||||
|
|
||||||
|
private ObjectMapper jsonMapper;
|
||||||
|
|
||||||
|
public List<Rating> findCachedRatingsByBookId(Long bookId) {
|
||||||
|
return setOps.members("book-" + bookId)
|
||||||
|
.stream()
|
||||||
|
.map(rtId -> {
|
||||||
|
try {
|
||||||
|
return jsonMapper.readValue(valueOps.get(rtId), Rating.class);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rating findCachedRatingById(Long ratingId) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
return jsonMapper.readValue(valueOps.get("rating-" + ratingId), Rating.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Rating> findAllCachedRatings() {
|
||||||
|
List<Rating> ratings = null;
|
||||||
|
|
||||||
|
ratings = redisTemplate.keys("rating*")
|
||||||
|
.stream()
|
||||||
|
.map(rtId -> {
|
||||||
|
try {
|
||||||
|
|
||||||
|
return jsonMapper.readValue(valueOps.get(rtId), Rating.class);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return ratings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean createRating(Rating persisted) {
|
||||||
|
try {
|
||||||
|
valueOps.set("rating-" + persisted.getId(), jsonMapper.writeValueAsString(persisted));
|
||||||
|
setOps.add("book-" + persisted.getBookId(), "rating-" + persisted.getId());
|
||||||
|
return true;
|
||||||
|
} catch (JsonProcessingException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean updateRating(Rating persisted) {
|
||||||
|
try {
|
||||||
|
valueOps.set("rating-" + persisted.getId(), jsonMapper.writeValueAsString(persisted));
|
||||||
|
return true;
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean deleteRating(Long ratingId) {
|
||||||
|
Rating toDel;
|
||||||
|
try {
|
||||||
|
|
||||||
|
toDel = jsonMapper.readValue(valueOps.get("rating-" + ratingId), Rating.class);
|
||||||
|
setOps.remove("book-" + toDel.getBookId(), "rating-" + ratingId);
|
||||||
|
redisTemplate.delete("rating-" + ratingId);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
|
||||||
|
this.redisTemplate = new StringRedisTemplate(cacheConnectionFactory);
|
||||||
|
|
||||||
|
this.valueOps = redisTemplate.opsForValue();
|
||||||
|
this.setOps = redisTemplate.opsForSet();
|
||||||
|
|
||||||
|
jsonMapper = new ObjectMapper();
|
||||||
|
jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,16 @@
|
|||||||
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
|
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import com.google.common.base.Preconditions;
|
||||||
import java.util.Map;
|
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@ -17,30 +19,51 @@ public class RatingService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RatingRepository ratingRepository;
|
private RatingRepository ratingRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RatingCacheRepository cacheRepository;
|
||||||
|
|
||||||
|
@HystrixCommand(commandKey = "ratingsByBookIdFromDB", fallbackMethod = "findCachedRatingsByBookId")
|
||||||
|
public List<Rating> findRatingsByBookId(Long bookId) {
|
||||||
|
return ratingRepository.findRatingsByBookId(bookId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Rating> findCachedRatingsByBookId(Long bookId) {
|
||||||
|
return cacheRepository.findCachedRatingsByBookId(bookId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HystrixCommand(commandKey = "ratingsFromDB", fallbackMethod = "findAllCachedRatings")
|
||||||
|
public List<Rating> findAllRatings() {
|
||||||
|
return ratingRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Rating> findAllCachedRatings() {
|
||||||
|
return cacheRepository.findAllCachedRatings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HystrixCommand(commandKey = "ratingsByIdFromDB", fallbackMethod = "findCachedRatingById", ignoreExceptions = { RatingNotFoundException.class })
|
||||||
public Rating findRatingById(Long ratingId) {
|
public Rating findRatingById(Long ratingId) {
|
||||||
return Optional.ofNullable(ratingRepository.findOne(ratingId))
|
return Optional.ofNullable(ratingRepository.findOne(ratingId))
|
||||||
.orElseThrow(() -> new RatingNotFoundException("Rating not found. ID: " + ratingId));
|
.orElseThrow(() -> new RatingNotFoundException("Rating not found. ID: " + ratingId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Rating> findRatingsByBookId(Long bookId) {
|
public Rating findCachedRatingById(Long ratingId) {
|
||||||
return ratingRepository.findRatingsByBookId(bookId);
|
return cacheRepository.findCachedRatingById(ratingId);
|
||||||
}
|
|
||||||
|
|
||||||
public List<Rating> findAllRatings() {
|
|
||||||
return ratingRepository.findAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
@Transactional(propagation = Propagation.REQUIRED)
|
||||||
public Rating createRating(Rating rating) {
|
public Rating createRating(Rating rating) {
|
||||||
final Rating newRating = new Rating();
|
Rating newRating = new Rating();
|
||||||
newRating.setBookId(rating.getBookId());
|
newRating.setBookId(rating.getBookId());
|
||||||
newRating.setStars(rating.getStars());
|
newRating.setStars(rating.getStars());
|
||||||
return ratingRepository.save(newRating);
|
Rating persisted = ratingRepository.save(newRating);
|
||||||
|
cacheRepository.createRating(persisted);
|
||||||
|
return persisted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
@Transactional(propagation = Propagation.REQUIRED)
|
||||||
public void deleteRating(Long ratingId) {
|
public void deleteRating(Long ratingId) {
|
||||||
ratingRepository.delete(ratingId);
|
ratingRepository.delete(ratingId);
|
||||||
|
cacheRepository.deleteRating(ratingId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
@Transactional(propagation = Propagation.REQUIRED)
|
||||||
@ -54,7 +77,9 @@ public class RatingService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return ratingRepository.save(rating);
|
Rating persisted = ratingRepository.save(rating);
|
||||||
|
cacheRepository.updateRating(persisted);
|
||||||
|
return persisted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
@Transactional(propagation = Propagation.REQUIRED)
|
||||||
@ -64,4 +89,5 @@ public class RatingService {
|
|||||||
Preconditions.checkNotNull(ratingRepository.findOne(ratingId));
|
Preconditions.checkNotNull(ratingRepository.findOne(ratingId));
|
||||||
return ratingRepository.save(rating);
|
return ratingRepository.save(rating);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user