Skip to content

Commit

Permalink
Merge branch 'release/5.8'
Browse files Browse the repository at this point in the history
  • Loading branch information
mohamed-taman committed May 26, 2020
2 parents 94f3d14 + aa6c68a commit 4a5e38a
Show file tree
Hide file tree
Showing 19 changed files with 314 additions and 95 deletions.
46 changes: 45 additions & 1 deletion config/repo/store.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ logging:
# Custom configurations
app:
auth-server: http://localhost:9999/.well-known/jwks.json
product-service.host: product
product-service:
host: product
timeoutSec: 2
recommendation-service.host: recommendation
review-service.host: review

Expand Down Expand Up @@ -94,6 +96,48 @@ api:
The implementation of the delete method is idempotent, i.e. it can be called several times with the same response.
This means that a delete request of a non existing product will return <b>200 Ok</b>.
# Circuit breaker & Retry configurations
resilience4j.circuitbreaker:
backends:
product:
registerHealthIndicator: true
# meaning that if three or more of the last five calls are faults,
# then the circuit will open.
ringBufferSizeInClosedState: 5
failureRateThreshold: 50
# meaning that the circuit breaker will keep the circuit open for 10 seconds
# and then transition to the half-open state.
waitInterval: 10000
automaticTransitionFromOpenToHalfOpenEnabled: true
# meaning that the circuit breaker will decide whether the circuit shall be opened or
# closed based on the three first calls after the circuit has transitioned to the
# half-open state. Since the failureRateThreshold parameters are set to 50%, the circuit
# will be open again if two or all three calls fail. Otherwise, the circuit will be closed.
ringBufferSizeInHalfOpenState: 3
recordExceptions:
- org.springframework.web.client.HttpServerErrorException
- java.util.concurrent.TimeoutException
- java.io.IOException
# meaning that our two business exceptions will not be
# counted as faults in the circuit breaker.
ignoreExceptions:
- com.siriusxi.ms.store.util.exceptions.InvalidInputException
- com.siriusxi.ms.store.util.exceptions.NotFoundException

resilience4j.retry:
backends:
product:
# Number of retries before giving up, including the first call,
# a maximum of two retry attempts.
maxRetryAttempts: 3
# Wait time before the next retry attempt, We will wait one second between retries.
waitDuration: 1000 # 1 second
# A list of exceptions that shall trigger a retry,
# We will only trigger retries on InternalServerError exceptions,
# that is, when HTTP requests respond with a 500 status code
retryExceptions:
- org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError

# -----------------------------------------------
# This is a docker specific profile properties
# Also profiles could be separated in its owen file
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ services:
- CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
- CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
volumes:
- $PWD/config/keystore:/keystore
- ./config/keystore:/keystore
depends_on:
- config-server
- eureka
Expand Down Expand Up @@ -123,7 +123,7 @@ services:
- SPRING_SECURITY_USER_NAME=${CONFIG_SERVER_USR}
- SPRING_SECURITY_USER_PASSWORD=${CONFIG_SERVER_PWD}
volumes:
- $PWD/config/repo:/config/repo
- ./config/repo:/config/repo
restart: on-failure
## End - Config Server definition
# End - Cloud Infrastructure
Expand Down
Binary file modified docs/diagram/app_ms_landscape.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion store-base/store-build-chassis/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<properties>
<java.version>14</java.version>
<spring.cloud.version>2020.0.0-M1</spring.cloud.version>
<spring.cloud.version>2020-1.M1</spring.cloud.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
*
* @author mohamed.taman
* @version v1.0
* @version v5.8
* @since v3.0 codename Storm
*/
@Api("REST API for Springy Store products information.")
Expand Down Expand Up @@ -54,8 +54,11 @@ public interface StoreEndpoint extends StoreService {
})
@GetMapping(value = "products/{id}",
produces = APPLICATION_JSON_VALUE)
Mono<ProductAggregate> getProduct(@PathVariable int id);

@Override
Mono<ProductAggregate> getProduct(
@PathVariable int id,
@RequestParam(value = "delay", required = false, defaultValue = "0") int delay,
@RequestParam(value = "faultPercent",required = false, defaultValue = "0") int faultPercent);
/**
* Sample usage:
*
Expand All @@ -65,7 +68,7 @@ public interface StoreEndpoint extends StoreService {
*
* @param body of composite product elements definition.
* @since v3.0 codename Storm.
* @return
* @return Nothing.
*/
@ApiOperation(
value = "${api.product-composite.create-composite-product.description}",
Expand All @@ -88,6 +91,7 @@ public interface StoreEndpoint extends StoreService {
@PostMapping(
value = "products",
consumes = APPLICATION_JSON_VALUE)
@Override
Mono<Void> createProduct(@RequestBody ProductAggregate body);

/**
Expand All @@ -97,7 +101,7 @@ public interface StoreEndpoint extends StoreService {
*
* @param id is the product id to delete it.
* @since v3.0 codename Storm.
* @return
* @return Nothing.
*/
@ApiOperation(
value = "${api.product-composite.delete-composite-product.description}",
Expand All @@ -118,5 +122,6 @@ public interface StoreEndpoint extends StoreService {
""")
})
@DeleteMapping("products/{id}")
@Override
Mono<Void> deleteProduct(@PathVariable int id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* </ol>
*
* @author mohamed.taman
* @version v0.2
* @version v5.8
* @since v0.1
*/
public interface StoreService {
Expand All @@ -34,10 +34,14 @@ public interface StoreService {
*
* @see ProductAggregate
* @param id is the product id that you are looking for.
* @param delay Causes the getProduct API on the product microservice to delay its response.
* @param faultPercent Causes the getProduct API on the product microservice to throw an
* exception randomly with the probability specified by the query parameter,
* from 0 to 100%.
* @return the product, if found, else null.
* @since v0.1
*/
Mono<ProductAggregate> getProduct(int id);
Mono<ProductAggregate> getProduct(int id, int delay, int faultPercent);

/**
* Delete the product and all its relate reviews and recommendations from their repositories.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.siriusxi.ms.store.api.core.product;

import com.siriusxi.ms.store.api.core.product.dto.Product;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import reactor.core.publisher.Mono;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
Expand All @@ -13,7 +16,7 @@
*
* @see ProductService
* @author mohamed.taman
* @version v4.0
* @version v5.8
* @since v3.0 codename Storm
*/
@RequestMapping("products")
Expand All @@ -28,7 +31,10 @@ public interface ProductEndpoint extends ProductService {
* @return Product the product, if found, else null.
* @since v3.0 codename Storm
*/
@Override
@GetMapping(value = "{productId}", produces = APPLICATION_JSON_VALUE)
Mono<Product> getProduct(@PathVariable("productId") int id);
@Override
Mono<Product> getProduct(
@PathVariable("productId") int id,
@RequestParam(value = "delay", required = false, defaultValue = "0") int delay,
@RequestParam(value = "faultPercent", required = false, defaultValue = "0") int faultPercent);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,34 @@
* </ol>
*
* @author mohamed.taman
* @version v0.2
* @version v5.8
* @since v0.1
*/
public interface ProductService {

/**
* Get the product with Id from repository.
* It is a Non-Blocking API.
* Get the product with Id from repository. It is a Non-Blocking API.
*
* @param id is the product id that you are looking for.
* @param id is the product id that you are looking for. * @param delay Causes the getProduct API
* on the product microservice to delay its response. * The parameter is specified in seconds
* @param faultPercent Causes the getProduct API on the product microservice to throw an exception
* randomly with the probability specified by the query parameter, * from 0 to 100%. For
* example, if the parameter is set to 25, it will cause * every fourth call to the API, on
* average, to fail with an exception.
* @return the product, if found, else null.
* @since v0.1
*/
Mono<Product> getProduct(int id);
Mono<Product> getProduct(int id, int delay, int faultPercent);

/**
* Add product to the repository.
*
* @param body product to save.
* @since v0.1
*/
default Product createProduct(Product body){ return null;}
default Product createProduct(Product body) {
return null;
}

/**
* Delete the product from repository.
Expand All @@ -42,5 +48,5 @@ public interface ProductService {
* @param id to be deleted.
* @since v0.1
*/
default void deleteProduct(int id){}
default void deleteProduct(int id) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ public interface RecommendationEndpoint extends RecommendationService {
* @since v3.0 codename Storm
*/
@GetMapping(produces = APPLICATION_JSON_VALUE)
@Override
Flux<Recommendation> getRecommendations(@RequestParam("productId") int productId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ public interface ReviewEndpoint extends ReviewService {
* @since v3.0 codename Storm
*/
@GetMapping(produces = APPLICATION_JSON_VALUE)
@Override
Flux<Review> getReviews(@RequestParam("productId") int productId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*
* @see ProductEndpoint
* @author mohamed.taman
* @version v4.0
* @version v5.8
* @since v3.0 codename Storm
*/
@RestController
Expand All @@ -32,7 +32,7 @@ public ProductController(@Qualifier("ProductServiceImpl") ProductService prodSer

/** {@inheritDoc} */
@Override
public Mono<Product> getProduct(int id) {
return prodService.getProduct(id);
public Mono<Product> getProduct(int id, int delay, int faultPercent) {
return prodService.getProduct(id, delay, faultPercent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.Random;

import static reactor.core.publisher.Mono.error;

@Service("ProductServiceImpl")
Expand All @@ -23,6 +26,7 @@ public class ProductServiceImpl implements ProductService {
private final ProductRepository repository;

private final ProductMapper mapper;
private final Random randomNumberGenerator = new Random();

@Autowired
public ProductServiceImpl(
Expand All @@ -38,26 +42,31 @@ public Product createProduct(Product body) {
isValidProductId(body.getProductId());

return repository
.save(mapper.apiToEntity(body))
.log()
.onErrorMap(
DuplicateKeyException.class,
ex -> new InvalidInputException("Duplicate key, Product Id: " + body.getProductId()))
.map(mapper::entityToApi)
.block();
.save(mapper.apiToEntity(body))
.log()
.onErrorMap(
DuplicateKeyException.class,
ex -> new InvalidInputException("Duplicate key, Product Id: " + body.getProductId()))
.map(mapper::entityToApi)
.block();
}

@Override
public Mono<Product> getProduct(int productId) {
public Mono<Product> getProduct(int productId, int delay, int faultPercent) {

isValidProductId(productId);

if (delay > 0) simulateDelay(delay);

if (faultPercent > 0) throwErrorIfBadLuck(faultPercent);

return repository
.findByProductId(productId)
.switchIfEmpty(error(new NotFoundException("No product found for productId: " + productId)))
.log()
.map(mapper::entityToApi)
.map(e -> {
.findByProductId(productId)
.switchIfEmpty(error(new NotFoundException("No product found for productId: " + productId)))
.log()
.map(mapper::entityToApi)
.map(
e -> {
e.setServiceAddress(serviceUtil.getServiceAddress());
return e;
});
Expand All @@ -74,16 +83,39 @@ public void deleteProduct(int productId) {

log.debug("deleteProduct: tries to delete an entity with productId: {}", productId);

repository
.findByProductId(productId)
.log()
.map(repository::delete)
.flatMap(e -> e)
.block();
repository.findByProductId(productId).log().map(repository::delete).flatMap(e -> e).block();
}

// TODO could be added to a utility class to be used by all core services implementations.
private void isValidProductId(int productId) {
if (productId < 1) throw new InvalidInputException("Invalid productId: " + productId);
}

private void simulateDelay(int delay) {
log.debug("Sleeping for {} seconds...", delay);
try {
Thread.sleep(Duration.ofSeconds(delay).toMillis());
} catch (InterruptedException ignored) {
}
log.debug("Moving on...");
}

private void throwErrorIfBadLuck(int faultPercent) {
int randomThreshold = getRandomNumber(1, 100);
if (faultPercent < randomThreshold) {
log.debug("We got lucky, no error occurred, {} < {}", faultPercent, randomThreshold);
} else {
log.debug("Bad luck, an error occurred, {} >= {}", faultPercent, randomThreshold);
throw new RuntimeException("Something went wrong...");
}
}

private int getRandomNumber(int min, int max) {

if (max < min) {
throw new RuntimeException("Max must be greater than min");
}

return randomNumberGenerator.nextInt((max - min) + 1) + min;
}
}
Loading

0 comments on commit 4a5e38a

Please sign in to comment.