Merge pull request #2211 from eugenp/bael-951-responsive-services

bael-951-responsive-services
This commit is contained in:
slavisa-baeldung 2017-07-05 17:05:36 +02:00 committed by GitHub
commit 026c9717ce
7 changed files with 305 additions and 84 deletions

View File

@ -1,78 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung.spring.cloud</groupId>
<artifactId>svc-rating</artifactId>
<version>1.0.0-SNAPSHOT</version>
<groupId>com.baeldung.spring.cloud</groupId>
<artifactId>svc-rating</artifactId>
<version>1.0.0-SNAPSHOT</version>
<parent>
<artifactId>parent-boot-4</artifactId>
<groupId>com.baeldung</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-4</relativePath>
</parent>
<parent>
<artifactId>parent-boot-4</artifactId>
<groupId>com.baeldung</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-4</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</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>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<spring-cloud-dependencies.version>Brixton.SR7</spring-cloud-dependencies.version>
</properties>
<properties>
<spring-cloud-dependencies.version>Brixton.SR7</spring-cloud-dependencies.version>
</properties>
</project>

View File

@ -1,21 +1,32 @@
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.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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.zipkin.HttpZipkinSpanReporter;
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
import org.springframework.context.annotation.AdviceMode;
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;
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
@EnableTransactionManagement(order=Ordered.LOWEST_PRECEDENCE, mode=AdviceMode.ASPECTJ)
public class RatingServiceApplication {
@Autowired
private EurekaClient eurekaClient;
@ -48,4 +59,11 @@ public class RatingServiceApplication {
}
};
}
@Bean
@Primary
@Order(value=Ordered.HIGHEST_PRECEDENCE)
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
}

View File

@ -20,17 +20,20 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.disable()
http
.authorizeRequests()
.regexMatchers("^/ratings\\?bookId.*$").authenticated()
.antMatchers(HttpMethod.POST,"/ratings").authenticated()
.antMatchers(HttpMethod.PATCH,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/ratings").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/hystrix").authenticated()
.anyRequest().authenticated()
.and()
.httpBasic().and()
.csrf()
.disable();
}
}

View File

@ -1,10 +1,28 @@
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.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.web.context.AbstractHttpSessionApplicationInitializer;
@Configuration
@EnableRedisHttpSession
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;
}
}

View File

@ -1,22 +1,34 @@
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.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Transient;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class Rating {
public class Rating implements Serializable {
/**
*
*/
private static final long serialVersionUID = 3308900941650386473L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long bookId;
private int stars;
@Transient
private boolean fromCache;
@Transient
private Long cachedTS = -1L;
public Rating() {
}
@ -54,4 +66,21 @@ public class Rating {
public void setStars(int 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;
}
}

View File

@ -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);
}
}

View File

@ -1,14 +1,16 @@
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.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.common.base.Preconditions;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@Service
@Transactional(readOnly = true)
@ -17,30 +19,51 @@ public class RatingService {
@Autowired
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) {
return Optional.ofNullable(ratingRepository.findOne(ratingId))
.orElseThrow(() -> new RatingNotFoundException("Rating not found. ID: " + ratingId));
}
public List<Rating> findRatingsByBookId(Long bookId) {
return ratingRepository.findRatingsByBookId(bookId);
}
public List<Rating> findAllRatings() {
return ratingRepository.findAll();
public Rating findCachedRatingById(Long ratingId) {
return cacheRepository.findCachedRatingById(ratingId);
}
@Transactional(propagation = Propagation.REQUIRED)
public Rating createRating(Rating rating) {
final Rating newRating = new Rating();
Rating newRating = new Rating();
newRating.setBookId(rating.getBookId());
newRating.setStars(rating.getStars());
return ratingRepository.save(newRating);
Rating persisted = ratingRepository.save(newRating);
cacheRepository.createRating(persisted);
return persisted;
}
@Transactional(propagation = Propagation.REQUIRED)
public void deleteRating(Long ratingId) {
ratingRepository.delete(ratingId);
cacheRepository.deleteRating(ratingId);
}
@Transactional(propagation = Propagation.REQUIRED)
@ -54,7 +77,9 @@ public class RatingService {
break;
}
});
return ratingRepository.save(rating);
Rating persisted = ratingRepository.save(rating);
cacheRepository.updateRating(persisted);
return persisted;
}
@Transactional(propagation = Propagation.REQUIRED)
@ -64,4 +89,5 @@ public class RatingService {
Preconditions.checkNotNull(ratingRepository.findOne(ratingId));
return ratingRepository.save(rating);
}
}