BAEL-951 Self-Healing Services with Spring Cloud - C2

This commit is contained in:
Ahamed Mustafa 2017-06-20 19:41:05 +01:00 committed by slavisa-baeldung
parent c03ca0e998
commit 0021df4637
8 changed files with 113 additions and 99 deletions

View File

@ -16,8 +16,5 @@ logging.level.org.springframework.security=debug
spring.redis.host=localhost spring.redis.host=localhost
spring.redis.port=6379 spring.redis.port=6379
cache.redis.hostName=localhost
cache.redis.port=6379
spring.sleuth.sampler.percentage=1.0 spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*) spring.sleuth.web.skipPattern=(^cleanup.*)

View File

@ -2,13 +2,9 @@ package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
@Configuration
@EnableConfigurationProperties({CacheProperties.class})
public class CacheConfig { public class CacheConfig {
@Autowired @Autowired
@ -17,8 +13,6 @@ public class CacheConfig {
@Bean(name="cacheConnectionFactory") @Bean(name="cacheConnectionFactory")
@Qualifier("cacheJedisConnectionFactory") @Qualifier("cacheJedisConnectionFactory")
JedisConnectionFactory cacheConnectionFactory() { JedisConnectionFactory cacheConnectionFactory() {
System.out.println(">>>>>>>>>>>>>>>>>>>>>Qualified Jedis Conn.Name.."+cacheProperties.hostName);
System.out.println(">>>>>>>>>>>>>>>>>>>>>Qualified Jedis Conn.Port.."+cacheProperties.port);
JedisConnectionFactory factory = new JedisConnectionFactory(); JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(cacheProperties.hostName); factory.setHostName(cacheProperties.hostName);
factory.setPort(cacheProperties.port); factory.setPort(cacheProperties.port);

View File

@ -1,8 +1,5 @@
package com.baeldung.spring.cloud.bootstrap.svcrating; package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix="cache.redis")
public class CacheProperties { public class CacheProperties {
public String hostName; public String hostName;
public int port; public int port;

View File

@ -10,16 +10,23 @@ 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.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient; 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 @EnableHystrix
@EnableTransactionManagement(order=Ordered.LOWEST_PRECEDENCE, mode=AdviceMode.ASPECTJ)
public class RatingServiceApplication { public class RatingServiceApplication {
@Autowired @Autowired
private EurekaClient eurekaClient; private EurekaClient eurekaClient;
@ -52,4 +59,11 @@ public class RatingServiceApplication {
} }
}; };
} }
@Bean
@Primary
@Order(value=Ordered.HIGHEST_PRECEDENCE)
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
} }

View File

@ -24,11 +24,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.authorizeRequests() .authorizeRequests()
.regexMatchers("^/ratings\\?bookId.*$").authenticated() .regexMatchers("^/ratings\\?bookId.*$").authenticated()
.antMatchers(HttpMethod.POST,"/ratings").authenticated() .antMatchers(HttpMethod.POST,"/ratings").authenticated()
.antMatchers(HttpMethod.GET,"/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*").permitAll() .antMatchers(HttpMethod.GET,"/hystrix").authenticated()
.anyRequest().authenticated() .anyRequest().authenticated()
.and() .and()
.httpBasic().and() .httpBasic().and()

View File

@ -12,7 +12,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@Entity @Entity
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class Rating implements Serializable{ public class Rating implements Serializable {
/** /**
* *
@ -27,8 +27,8 @@ public class Rating implements Serializable{
@Transient @Transient
private boolean fromCache; private boolean fromCache;
@Transient @Transient
private Long cachedTS=-1L; private Long cachedTS = -1L;
public Rating() { public Rating() {
} }
@ -83,23 +83,4 @@ public class Rating implements Serializable{
this.cachedTS = cachedTS; this.cachedTS = cachedTS;
} }
@Override
public String toString() {
return "Rating [" + id + "," + bookId + "," + stars + "," + cachedTS + "]";
}
public static Rating fromString(String ratingAsStr){
if(ratingAsStr == null || ratingAsStr.isEmpty())
return null;
String[] attributeVals=ratingAsStr.substring(8,ratingAsStr.length()-1).split("[,]");
Rating rating=new Rating();
rating.setId(Long.valueOf(attributeVals[0]));
rating.setBookId(Long.valueOf(attributeVals[1]));
rating.setStars(Integer.valueOf(attributeVals[2]));
rating.setCachedTS(Long.valueOf(attributeVals[3]));
return rating;
}
} }

View File

@ -1,77 +1,120 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating; package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.SetOperations; import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
@Repository @Repository
public class RatingCacheRepositoryImpl implements InitializingBean, RatingCacheRepository { public class RatingCacheRepositoryImpl implements InitializingBean, RatingCacheRepository {
@Autowired @Autowired
@Qualifier("cacheJedisConnectionFactory")
private JedisConnectionFactory cacheConnectionFactory; private JedisConnectionFactory cacheConnectionFactory;
private StringRedisTemplate redisTemplate;
private ValueOperations<String,String> valueOps;
private SetOperations<String,String> setOps;
public List<Rating> findCachedRatingsByBookId(Long bookId){ 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) return setOps.members("book-" + bookId)
.stream() .stream()
.map(rtId -> Rating.fromString(valueOps.get(rtId))) .map(rtId -> {
.map(rt -> { try {
rt.setFromCache(true); Rating rt = jsonMapper.readValue(valueOps.get(rtId), Rating.class);
return rt; rt.setFromCache(true);
return rt;
} catch (IOException ex) {
return null;
}
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public Rating findCachedRatingById(Long ratingId) { public Rating findCachedRatingById(Long ratingId) {
return Rating.fromString(valueOps.get("rating-" + ratingId));
try {
return jsonMapper.readValue(valueOps.get("rating-" + ratingId), Rating.class);
} catch (IOException e) {
return null;
}
} }
public List<Rating> findAllCachedRatings(){ public List<Rating> findAllCachedRatings() {
return redisTemplate.keys("rating*") List<Rating> ratings = null;
ratings = redisTemplate.keys("rating*")
.stream() .stream()
.map(rtId -> Rating.fromString(valueOps.get(rtId))) .map(rtId -> {
try {
return jsonMapper.readValue(valueOps.get(rtId), Rating.class);
} catch (IOException e) {
return null;
}
})
.collect(Collectors.toList()); .collect(Collectors.toList());
return ratings;
} }
public boolean createRating(Rating persisted){ public boolean createRating(Rating persisted) {
valueOps.set("rating-"+persisted.getId(), persisted.toString()); try {
setOps.add("book-"+persisted.getBookId(), "rating-"+persisted.getId()); valueOps.set("rating-" + persisted.getId(), jsonMapper.writeValueAsString(persisted));
return true; setOps.add("book-" + persisted.getBookId(), "rating-" + persisted.getId());
return true;
} catch (JsonProcessingException ex) {
return false;
}
} }
public boolean updateRating(Rating persisted){ public boolean updateRating(Rating persisted) {
valueOps.set("rating-"+persisted.getId(), persisted.toString()); try {
return true; valueOps.set("rating-" + persisted.getId(), jsonMapper.writeValueAsString(persisted));
return true;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return false;
} }
public boolean deleteRating(Long ratingId){ public boolean deleteRating(Long ratingId) {
Rating toDel=Rating.fromString(valueOps.get("rating-"+ratingId)); Rating toDel;
setOps.remove("book-"+toDel.getBookId(), "rating-"+ratingId); try {
redisTemplate.delete("rating-"+ratingId);
return true; 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 @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
this.redisTemplate = new StringRedisTemplate(cacheConnectionFactory); this.redisTemplate = new StringRedisTemplate(cacheConnectionFactory);
this.valueOps = redisTemplate.opsForValue(); this.valueOps = redisTemplate.opsForValue();
this.setOps=redisTemplate.opsForSet(); this.setOps = redisTemplate.opsForSet();
jsonMapper = new ObjectMapper();
jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
} }
} }

View File

@ -1,6 +1,5 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating; package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -15,58 +14,48 @@ import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@Service @Service
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class RatingService{ public class RatingService {
@Autowired @Autowired
private RatingRepository ratingRepository; private RatingRepository ratingRepository;
@Autowired @Autowired
private RatingCacheRepository cacheRepository; private RatingCacheRepository cacheRepository;
@HystrixCommand(commandKey="ratingsByBookIdFromDB",fallbackMethod="findCachedRatingsByBookId") @HystrixCommand(commandKey = "ratingsByBookIdFromDB", fallbackMethod = "findCachedRatingsByBookId")
public List<Rating> findRatingsByBookId(Long bookId) { public List<Rating> findRatingsByBookId(Long bookId) {
if(bookId==2)
throw new IllegalArgumentException("BookID 2 redirects to cache");
return ratingRepository.findRatingsByBookId(bookId); return ratingRepository.findRatingsByBookId(bookId);
} }
@HystrixCommand(commandKey="ratingsByBookIdFromCache",fallbackMethod="defaultRatingsByBookId") public List<Rating> findCachedRatingsByBookId(Long bookId) {
public List<Rating> findCachedRatingsByBookId(Long bookId){
return cacheRepository.findCachedRatingsByBookId(bookId); return cacheRepository.findCachedRatingsByBookId(bookId);
} }
public List<Rating> defaultRatingsByBookId(Long bookId){
return Collections.emptyList();
}
@HystrixCommand(commandKey = "ratingsFromDB", fallbackMethod = "findAllCachedRatings")
@HystrixCommand(commandKey="ratingsFromDB",fallbackMethod="findAllCachedRatings")
public List<Rating> findAllRatings() { public List<Rating> findAllRatings() {
return ratingRepository.findAll(); return ratingRepository.findAll();
} }
public List<Rating> findAllCachedRatings(){ public List<Rating> findAllCachedRatings() {
return cacheRepository.findAllCachedRatings(); return cacheRepository.findAllCachedRatings();
} }
@HystrixCommand(commandKey = "ratingsByIdFromDB", fallbackMethod = "findCachedRatingById", ignoreExceptions = { RatingNotFoundException.class }) @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 Rating findCachedRatingById(Long ratingId){ public Rating findCachedRatingById(Long ratingId) {
return cacheRepository.findCachedRatingById(ratingId); return cacheRepository.findCachedRatingById(ratingId);
} }
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public Rating createRating(Rating rating) { public Rating createRating(Rating rating) {
Rating newRating = new Rating(); Rating newRating = new Rating();
newRating.setBookId(rating.getBookId()); newRating.setBookId(rating.getBookId());
newRating.setStars(rating.getStars()); newRating.setStars(rating.getStars());
Rating persisted=ratingRepository.save(newRating); Rating persisted = ratingRepository.save(newRating);
cacheRepository.createRating(persisted); cacheRepository.createRating(persisted);
return persisted; return persisted;
} }
@ -88,7 +77,7 @@ public class RatingService{
break; break;
} }
}); });
Rating persisted= ratingRepository.save(rating); Rating persisted = ratingRepository.save(rating);
cacheRepository.updateRating(persisted); cacheRepository.updateRating(persisted);
return persisted; return persisted;
} }