Skip to content

Commit 4a5e38a

Browse files
committed
Merge branch 'release/5.8'
2 parents 94f3d14 + aa6c68a commit 4a5e38a

File tree

19 files changed

+314
-95
lines changed

19 files changed

+314
-95
lines changed

config/repo/store.yml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ logging:
3535
# Custom configurations
3636
app:
3737
auth-server: http://localhost:9999/.well-known/jwks.json
38-
product-service.host: product
38+
product-service:
39+
host: product
40+
timeoutSec: 2
3941
recommendation-service.host: recommendation
4042
review-service.host: review
4143

@@ -94,6 +96,48 @@ api:
9496
The implementation of the delete method is idempotent, i.e. it can be called several times with the same response.
9597
This means that a delete request of a non existing product will return <b>200 Ok</b>.
9698
99+
# Circuit breaker & Retry configurations
100+
resilience4j.circuitbreaker:
101+
backends:
102+
product:
103+
registerHealthIndicator: true
104+
# meaning that if three or more of the last five calls are faults,
105+
# then the circuit will open.
106+
ringBufferSizeInClosedState: 5
107+
failureRateThreshold: 50
108+
# meaning that the circuit breaker will keep the circuit open for 10 seconds
109+
# and then transition to the half-open state.
110+
waitInterval: 10000
111+
automaticTransitionFromOpenToHalfOpenEnabled: true
112+
# meaning that the circuit breaker will decide whether the circuit shall be opened or
113+
# closed based on the three first calls after the circuit has transitioned to the
114+
# half-open state. Since the failureRateThreshold parameters are set to 50%, the circuit
115+
# will be open again if two or all three calls fail. Otherwise, the circuit will be closed.
116+
ringBufferSizeInHalfOpenState: 3
117+
recordExceptions:
118+
- org.springframework.web.client.HttpServerErrorException
119+
- java.util.concurrent.TimeoutException
120+
- java.io.IOException
121+
# meaning that our two business exceptions will not be
122+
# counted as faults in the circuit breaker.
123+
ignoreExceptions:
124+
- com.siriusxi.ms.store.util.exceptions.InvalidInputException
125+
- com.siriusxi.ms.store.util.exceptions.NotFoundException
126+
127+
resilience4j.retry:
128+
backends:
129+
product:
130+
# Number of retries before giving up, including the first call,
131+
# a maximum of two retry attempts.
132+
maxRetryAttempts: 3
133+
# Wait time before the next retry attempt, We will wait one second between retries.
134+
waitDuration: 1000 # 1 second
135+
# A list of exceptions that shall trigger a retry,
136+
# We will only trigger retries on InternalServerError exceptions,
137+
# that is, when HTTP requests respond with a 500 status code
138+
retryExceptions:
139+
- org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError
140+
97141
# -----------------------------------------------
98142
# This is a docker specific profile properties
99143
# Also profiles could be separated in its owen file

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ services:
9292
- CONFIG_SERVER_USR=${CONFIG_SERVER_USR}
9393
- CONFIG_SERVER_PWD=${CONFIG_SERVER_PWD}
9494
volumes:
95-
- $PWD/config/keystore:/keystore
95+
- ./config/keystore:/keystore
9696
depends_on:
9797
- config-server
9898
- eureka
@@ -123,7 +123,7 @@ services:
123123
- SPRING_SECURITY_USER_NAME=${CONFIG_SERVER_USR}
124124
- SPRING_SECURITY_USER_PASSWORD=${CONFIG_SERVER_PWD}
125125
volumes:
126-
- $PWD/config/repo:/config/repo
126+
- ./config/repo:/config/repo
127127
restart: on-failure
128128
## End - Config Server definition
129129
# End - Cloud Infrastructure

docs/diagram/app_ms_landscape.png

4.07 KB
Loading

store-base/store-build-chassis/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
<properties>
2121
<java.version>14</java.version>
22-
<spring.cloud.version>2020.0.0-M1</spring.cloud.version>
22+
<spring.cloud.version>2020-1.M1</spring.cloud.version>
2323
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2424
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
2525

store-common/store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreEndpoint.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*
1818
*
1919
* @author mohamed.taman
20-
* @version v1.0
20+
* @version v5.8
2121
* @since v3.0 codename Storm
2222
*/
2323
@Api("REST API for Springy Store products information.")
@@ -54,8 +54,11 @@ public interface StoreEndpoint extends StoreService {
5454
})
5555
@GetMapping(value = "products/{id}",
5656
produces = APPLICATION_JSON_VALUE)
57-
Mono<ProductAggregate> getProduct(@PathVariable int id);
58-
57+
@Override
58+
Mono<ProductAggregate> getProduct(
59+
@PathVariable int id,
60+
@RequestParam(value = "delay", required = false, defaultValue = "0") int delay,
61+
@RequestParam(value = "faultPercent",required = false, defaultValue = "0") int faultPercent);
5962
/**
6063
* Sample usage:
6164
*
@@ -65,7 +68,7 @@ public interface StoreEndpoint extends StoreService {
6568
*
6669
* @param body of composite product elements definition.
6770
* @since v3.0 codename Storm.
68-
* @return
71+
* @return Nothing.
6972
*/
7073
@ApiOperation(
7174
value = "${api.product-composite.create-composite-product.description}",
@@ -88,6 +91,7 @@ public interface StoreEndpoint extends StoreService {
8891
@PostMapping(
8992
value = "products",
9093
consumes = APPLICATION_JSON_VALUE)
94+
@Override
9195
Mono<Void> createProduct(@RequestBody ProductAggregate body);
9296

9397
/**
@@ -97,7 +101,7 @@ public interface StoreEndpoint extends StoreService {
97101
*
98102
* @param id is the product id to delete it.
99103
* @since v3.0 codename Storm.
100-
* @return
104+
* @return Nothing.
101105
*/
102106
@ApiOperation(
103107
value = "${api.product-composite.delete-composite-product.description}",
@@ -118,5 +122,6 @@ public interface StoreEndpoint extends StoreService {
118122
""")
119123
})
120124
@DeleteMapping("products/{id}")
125+
@Override
121126
Mono<Void> deleteProduct(@PathVariable int id);
122127
}

store-common/store-api/src/main/java/com/siriusxi/ms/store/api/composite/StoreService.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* </ol>
1313
*
1414
* @author mohamed.taman
15-
* @version v0.2
15+
* @version v5.8
1616
* @since v0.1
1717
*/
1818
public interface StoreService {
@@ -34,10 +34,14 @@ public interface StoreService {
3434
*
3535
* @see ProductAggregate
3636
* @param id is the product id that you are looking for.
37+
* @param delay Causes the getProduct API on the product microservice to delay its response.
38+
* @param faultPercent Causes the getProduct API on the product microservice to throw an
39+
* exception randomly with the probability specified by the query parameter,
40+
* from 0 to 100%.
3741
* @return the product, if found, else null.
3842
* @since v0.1
3943
*/
40-
Mono<ProductAggregate> getProduct(int id);
44+
Mono<ProductAggregate> getProduct(int id, int delay, int faultPercent);
4145

4246
/**
4347
* Delete the product and all its relate reviews and recommendations from their repositories.

store-common/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductEndpoint.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.siriusxi.ms.store.api.core.product;
22

33
import com.siriusxi.ms.store.api.core.product.dto.Product;
4-
import org.springframework.web.bind.annotation.*;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.PathVariable;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RequestParam;
58
import reactor.core.publisher.Mono;
69

710
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@@ -13,7 +16,7 @@
1316
*
1417
* @see ProductService
1518
* @author mohamed.taman
16-
* @version v4.0
19+
* @version v5.8
1720
* @since v3.0 codename Storm
1821
*/
1922
@RequestMapping("products")
@@ -28,7 +31,10 @@ public interface ProductEndpoint extends ProductService {
2831
* @return Product the product, if found, else null.
2932
* @since v3.0 codename Storm
3033
*/
31-
@Override
3234
@GetMapping(value = "{productId}", produces = APPLICATION_JSON_VALUE)
33-
Mono<Product> getProduct(@PathVariable("productId") int id);
35+
@Override
36+
Mono<Product> getProduct(
37+
@PathVariable("productId") int id,
38+
@RequestParam(value = "delay", required = false, defaultValue = "0") int delay,
39+
@RequestParam(value = "faultPercent", required = false, defaultValue = "0") int faultPercent);
3440
}

store-common/store-api/src/main/java/com/siriusxi/ms/store/api/core/product/ProductService.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,34 @@
1212
* </ol>
1313
*
1414
* @author mohamed.taman
15-
* @version v0.2
15+
* @version v5.8
1616
* @since v0.1
1717
*/
1818
public interface ProductService {
1919

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

3034
/**
3135
* Add product to the repository.
3236
*
3337
* @param body product to save.
3438
* @since v0.1
3539
*/
36-
default Product createProduct(Product body){ return null;}
40+
default Product createProduct(Product body) {
41+
return null;
42+
}
3743

3844
/**
3945
* Delete the product from repository.
@@ -42,5 +48,5 @@ public interface ProductService {
4248
* @param id to be deleted.
4349
* @since v0.1
4450
*/
45-
default void deleteProduct(int id){}
51+
default void deleteProduct(int id) {}
4652
}

store-common/store-api/src/main/java/com/siriusxi/ms/store/api/core/recommendation/RecommendationEndpoint.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ public interface RecommendationEndpoint extends RecommendationService {
3030
* @since v3.0 codename Storm
3131
*/
3232
@GetMapping(produces = APPLICATION_JSON_VALUE)
33+
@Override
3334
Flux<Recommendation> getRecommendations(@RequestParam("productId") int productId);
3435
}

store-common/store-api/src/main/java/com/siriusxi/ms/store/api/core/review/ReviewEndpoint.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ public interface ReviewEndpoint extends ReviewService {
3131
* @since v3.0 codename Storm
3232
*/
3333
@GetMapping(produces = APPLICATION_JSON_VALUE)
34+
@Override
3435
Flux<Review> getReviews(@RequestParam("productId") int productId);
3536
}

store-services/product-service/src/main/java/com/siriusxi/ms/store/ps/api/ProductController.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*
1616
* @see ProductEndpoint
1717
* @author mohamed.taman
18-
* @version v4.0
18+
* @version v5.8
1919
* @since v3.0 codename Storm
2020
*/
2121
@RestController
@@ -32,7 +32,7 @@ public ProductController(@Qualifier("ProductServiceImpl") ProductService prodSer
3232

3333
/** {@inheritDoc} */
3434
@Override
35-
public Mono<Product> getProduct(int id) {
36-
return prodService.getProduct(id);
35+
public Mono<Product> getProduct(int id, int delay, int faultPercent) {
36+
return prodService.getProduct(id, delay, faultPercent);
3737
}
3838
}

store-services/product-service/src/main/java/com/siriusxi/ms/store/ps/service/ProductServiceImpl.java

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
import org.springframework.stereotype.Service;
1313
import reactor.core.publisher.Mono;
1414

15+
import java.time.Duration;
16+
import java.util.Random;
17+
1518
import static reactor.core.publisher.Mono.error;
1619

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

2528
private final ProductMapper mapper;
29+
private final Random randomNumberGenerator = new Random();
2630

2731
@Autowired
2832
public ProductServiceImpl(
@@ -38,26 +42,31 @@ public Product createProduct(Product body) {
3842
isValidProductId(body.getProductId());
3943

4044
return repository
41-
.save(mapper.apiToEntity(body))
42-
.log()
43-
.onErrorMap(
44-
DuplicateKeyException.class,
45-
ex -> new InvalidInputException("Duplicate key, Product Id: " + body.getProductId()))
46-
.map(mapper::entityToApi)
47-
.block();
45+
.save(mapper.apiToEntity(body))
46+
.log()
47+
.onErrorMap(
48+
DuplicateKeyException.class,
49+
ex -> new InvalidInputException("Duplicate key, Product Id: " + body.getProductId()))
50+
.map(mapper::entityToApi)
51+
.block();
4852
}
4953

5054
@Override
51-
public Mono<Product> getProduct(int productId) {
55+
public Mono<Product> getProduct(int productId, int delay, int faultPercent) {
5256

5357
isValidProductId(productId);
5458

59+
if (delay > 0) simulateDelay(delay);
60+
61+
if (faultPercent > 0) throwErrorIfBadLuck(faultPercent);
62+
5563
return repository
56-
.findByProductId(productId)
57-
.switchIfEmpty(error(new NotFoundException("No product found for productId: " + productId)))
58-
.log()
59-
.map(mapper::entityToApi)
60-
.map(e -> {
64+
.findByProductId(productId)
65+
.switchIfEmpty(error(new NotFoundException("No product found for productId: " + productId)))
66+
.log()
67+
.map(mapper::entityToApi)
68+
.map(
69+
e -> {
6170
e.setServiceAddress(serviceUtil.getServiceAddress());
6271
return e;
6372
});
@@ -74,16 +83,39 @@ public void deleteProduct(int productId) {
7483

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

77-
repository
78-
.findByProductId(productId)
79-
.log()
80-
.map(repository::delete)
81-
.flatMap(e -> e)
82-
.block();
86+
repository.findByProductId(productId).log().map(repository::delete).flatMap(e -> e).block();
8387
}
8488

8589
// TODO could be added to a utility class to be used by all core services implementations.
8690
private void isValidProductId(int productId) {
8791
if (productId < 1) throw new InvalidInputException("Invalid productId: " + productId);
8892
}
93+
94+
private void simulateDelay(int delay) {
95+
log.debug("Sleeping for {} seconds...", delay);
96+
try {
97+
Thread.sleep(Duration.ofSeconds(delay).toMillis());
98+
} catch (InterruptedException ignored) {
99+
}
100+
log.debug("Moving on...");
101+
}
102+
103+
private void throwErrorIfBadLuck(int faultPercent) {
104+
int randomThreshold = getRandomNumber(1, 100);
105+
if (faultPercent < randomThreshold) {
106+
log.debug("We got lucky, no error occurred, {} < {}", faultPercent, randomThreshold);
107+
} else {
108+
log.debug("Bad luck, an error occurred, {} >= {}", faultPercent, randomThreshold);
109+
throw new RuntimeException("Something went wrong...");
110+
}
111+
}
112+
113+
private int getRandomNumber(int min, int max) {
114+
115+
if (max < min) {
116+
throw new RuntimeException("Max must be greater than min");
117+
}
118+
119+
return randomNumberGenerator.nextInt((max - min) + 1) + min;
120+
}
89121
}

0 commit comments

Comments
 (0)