diff --git a/configuration-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/configuration/ConfigurationServiceApplication.java b/configuration-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/configuration/ConfigurationServiceApplication.java index 7e49fa8..3c96dbd 100644 --- a/configuration-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/configuration/ConfigurationServiceApplication.java +++ b/configuration-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/configuration/ConfigurationServiceApplication.java @@ -9,15 +9,16 @@ @EnableConfigServer public class ConfigurationServiceApplication { + private static String FP; + @Value("${spring.cloud.config.server.native.search-locations}") - public void setBaseLocation(String baseLocation) { - BL = baseLocation; + public void setFP(String filePath) { + FP = filePath; } - static String BL; public static void main(String[] args) { SpringApplication.run(ConfigurationServiceApplication.class, args); - System.out.println(BL); + System.out.println(FP); } } diff --git a/configuration-service/src/main/resources/bootstrap.properties b/configuration-service/src/main/resources/bootstrap.properties index ac24950..e7b6539 100644 --- a/configuration-service/src/main/resources/bootstrap.properties +++ b/configuration-service/src/main/resources/bootstrap.properties @@ -6,4 +6,6 @@ server.port=8888 eureka.instance.prefer-ip-address=true -spring.cloud.loadbalancer.ribbon.enabled=false \ No newline at end of file +spring.cloud.loadbalancer.ribbon.enabled=false + +spring.cloud.config.server.native.search-locations=file:${PROJECT_BASE_DIR}/data/configuration-store \ No newline at end of file diff --git a/data/configuration-store/polling-service-development.properties b/data/configuration-store/polling-service-development.properties index 55786ca..a3d0a7a 100644 --- a/data/configuration-store/polling-service-development.properties +++ b/data/configuration-store/polling-service-development.properties @@ -7,12 +7,12 @@ spring.redis.port=6379 eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka ps.refresh.search.location=Stuttgart -ps.refresh.cron.expression=*/60 * * * * * -ps.cleanup.cron.expression=*/120 * * * * * -ps.cleanup.staleness.interval=30 -ps.cleanup.ttl.eviction=30 +ps.refresh.cron.expression=*/20 * * * * * +ps.cleanup.cron.expression=*/60 * * * * * +ps.cleanup.staleness.interval=10 +ps.cleanup.ttl.eviction=10 -car2go.base.url=http://192.168.0.109:3000 +car2go.base.url=http://192.168.0.111:3000 car2go.vehicles.by.location.uri=/vehicles/{locationName} #spring.redis.redisson.file=classpath:redisson.yaml diff --git a/data/configuration-store/position-service-development.properties b/data/configuration-store/position-service-development.properties index ea6fa4a..ede5a99 100644 --- a/data/configuration-store/position-service-development.properties +++ b/data/configuration-store/position-service-development.properties @@ -14,4 +14,9 @@ poss.car-service.url=localhost:17080/car poss.placement.infinite.end.limit=1000 -eureka.instance.prefer-ip-address=true \ No newline at end of file +eureka.instance.prefer-ip-address=true + +poss.circuit-breaker.failure.threshold-percentage=30 +poss.circuit-breaker.wait.duration.in-open-state=3000 +poss.circuit-breaker.sliding-window.size=2 +poss.circuit-breaker.timeout.duration=10 \ No newline at end of file diff --git a/data/configuration-store/position-service-staging.properties b/data/configuration-store/position-service-staging.properties index 2c6b5c8..d11bb1c 100644 --- a/data/configuration-store/position-service-staging.properties +++ b/data/configuration-store/position-service-staging.properties @@ -7,4 +7,9 @@ spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*) poss.placement.infinite.end.limit=1000 -eureka.instance.prefer-ip-address=true \ No newline at end of file +eureka.instance.prefer-ip-address=true + +poss.circuit-breaker.failure.threshold-percentage=30 +poss.circuit-breaker.wait.duration.in-open-state=3000 +poss.circuit-breaker.sliding-window.size=3 +poss.circuit-breaker.timeout.duration=3 \ No newline at end of file diff --git a/position-service/pom.xml b/position-service/pom.xml index 3d324da..6a6a762 100644 --- a/position-service/pom.xml +++ b/position-service/pom.xml @@ -61,6 +61,10 @@ jts-core ${jts.version} + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + diff --git a/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/configuration/PositionServiceConfiguration.java b/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/configuration/PositionServiceConfiguration.java index 858c769..9034b46 100644 --- a/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/configuration/PositionServiceConfiguration.java +++ b/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/configuration/PositionServiceConfiguration.java @@ -1,6 +1,12 @@ package com.teenthofabud.codingchallenge.sharenow.position.configuration; +import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.timelimiter.TimeLimiterConfig; import org.locationtech.jts.geom.GeometryFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory; +import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder; +import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; @@ -9,6 +15,7 @@ import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.i18n.SessionLocaleResolver; +import java.time.Duration; import java.util.Locale; @Component @@ -36,4 +43,25 @@ public GeometryFactory geometryFactory() { } + @Bean + public Customizer globalCircuitBreakerFactory( + @Value("${poss.circuit-breaker.failure.threshold-percentage}") float failureThresholdPercentage, + @Value("${poss.circuit-breaker.wait.duration.in-open-state}") long waitDurationInOpenStateInMillis, + @Value("${poss.circuit-breaker.sliding-window.size}") int slidingWindowSize, + @Value("${poss.circuit-breaker.timeout.duration}") long timeoutDurationInSeconds) { + + CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() + .failureRateThreshold(failureThresholdPercentage) + .waitDurationInOpenState(Duration.ofMillis(waitDurationInOpenStateInMillis)) + .slidingWindowSize(slidingWindowSize) + .build(); + TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom() + .timeoutDuration(Duration.ofSeconds(timeoutDurationInSeconds)) + .build(); + return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id) + .timeLimiterConfig(timeLimiterConfig) + .circuitBreakerConfig(circuitBreakerConfig) + .build()); + } + } diff --git a/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/service/impl/PlacementServiceJTSImpl.java b/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/service/impl/PlacementServiceJTSImpl.java index 1a3d722..7c573ff 100644 --- a/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/service/impl/PlacementServiceJTSImpl.java +++ b/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/service/impl/PlacementServiceJTSImpl.java @@ -36,14 +36,18 @@ public boolean isCarInsidePolygon(CarDetailsDTO carDTO, StrategicPolygonDetailed if (coordinates != null && coordinates.size() < 3) { throw new PositionServiceException("Invalid polygon", PositionErrorCode.UNEXPECTED_PARAMETER, new Object[] {"polygon vertex count < 3"}); } else { - List polygonPointsList = polygonDTO.getGeometry().getExteriorRing().stream() - .map(lla -> new Coordinate(lla.getLongitude(), lla.getLatitude())).collect(Collectors.toList()); - polygonPointsList.add(polygonPointsList.get(0)); // sanitizing polygon coordinates as per GeoJSOn specifications - Coordinate[] polygonPointsArray = polygonPointsList.toArray(new Coordinate[polygonPointsList.size()]); - Polygon strategicPolygon = geometryFactory.createPolygon(polygonPointsArray); - Coordinate carCoordinates = new Coordinate(carDTO.getPosition().getLongitude(), carDTO.getPosition().getLatitude()); - Point carLocation = geometryFactory.createPoint(carCoordinates); - return strategicPolygon.contains(carLocation) || strategicPolygon.getBoundary().contains(carLocation); + if(carDTO.getPosition() != null) { + Coordinate carCoordinates = new Coordinate(carDTO.getPosition().getLongitude(), carDTO.getPosition().getLatitude()); + List polygonPointsList = polygonDTO.getGeometry().getExteriorRing().stream() + .map(lla -> new Coordinate(lla.getLongitude(), lla.getLatitude())).collect(Collectors.toList()); + polygonPointsList.add(polygonPointsList.get(0)); // sanitizing polygon coordinates as per GeoJSOn specifications + Coordinate[] polygonPointsArray = polygonPointsList.toArray(new Coordinate[polygonPointsList.size()]); + Polygon strategicPolygon = geometryFactory.createPolygon(polygonPointsArray); + Point carLocation = geometryFactory.createPoint(carCoordinates); + return strategicPolygon.contains(carLocation) || strategicPolygon.getBoundary().contains(carLocation); + } else { + throw new PositionServiceException("Invalid car position", PositionErrorCode.UNEXPECTED_PARAMETER, new Object[] {"car position is not provided"}); + } } } else { throw new PositionServiceException("Invalid polygon", PositionErrorCode.INVALID_PARAMETER, new Object[] {"polygon coordinates"}); diff --git a/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/service/impl/PositionServiceImpl.java b/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/service/impl/PositionServiceImpl.java index 1470eb0..a106fc5 100644 --- a/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/service/impl/PositionServiceImpl.java +++ b/position-service/src/main/java/com/teenthofabud/codingchallenge/sharenow/position/service/impl/PositionServiceImpl.java @@ -1,6 +1,5 @@ package com.teenthofabud.codingchallenge.sharenow.position.service.impl; -import com.ctc.wstx.sw.EncodingXmlWriter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.teenthofabud.codingchallenge.sharenow.position.model.dto.car.CarDetailsDTO; @@ -9,11 +8,13 @@ import com.teenthofabud.codingchallenge.sharenow.position.model.dto.polygon.StrategicPolygonDetailedDTO; import com.teenthofabud.codingchallenge.sharenow.position.model.error.PositionErrorCode; import com.teenthofabud.codingchallenge.sharenow.position.model.error.PositionServiceException; -import com.teenthofabud.codingchallenge.sharenow.position.model.vo.*; +import com.teenthofabud.codingchallenge.sharenow.position.model.vo.ErrorVO; import com.teenthofabud.codingchallenge.sharenow.position.model.vo.car.Car2StrategicPolygonPositioningVO; import com.teenthofabud.codingchallenge.sharenow.position.model.vo.car.CarMappedVO; import com.teenthofabud.codingchallenge.sharenow.position.model.vo.car.PositionVO; -import com.teenthofabud.codingchallenge.sharenow.position.model.vo.polygon.*; +import com.teenthofabud.codingchallenge.sharenow.position.model.vo.polygon.GeoFeatureVO; +import com.teenthofabud.codingchallenge.sharenow.position.model.vo.polygon.StrategicPolygon2CarPositioningVO; +import com.teenthofabud.codingchallenge.sharenow.position.model.vo.polygon.StrategicPolygonMappedVO; import com.teenthofabud.codingchallenge.sharenow.position.repository.CarServiceClient; import com.teenthofabud.codingchallenge.sharenow.position.repository.PolygonServiceClient; import com.teenthofabud.codingchallenge.sharenow.position.service.PlacementService; @@ -22,6 +23,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; +import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; @@ -45,6 +48,13 @@ public class PositionServiceImpl implements PositionService { @Autowired private PolygonServiceClient polygonClient; + @Autowired + private CircuitBreakerFactory globalCircuitBreakerFactory; + + private CircuitBreaker carServiceCircuitBreaker; + + private CircuitBreaker polygonServiceCircuitBreaker; + private Converter carDetailsDTO2VOConverter; private Converter polygonDetailsDTO2VOConverter; @@ -55,6 +65,8 @@ public class PositionServiceImpl implements PositionService { @PostConstruct public void init() { + this.carServiceCircuitBreaker = globalCircuitBreakerFactory.create("car"); + this.polygonServiceCircuitBreaker = globalCircuitBreakerFactory.create("polygon"); this.positionDTO2VOConverter = (dto) -> { PositionVO vo = new PositionVO(); vo.setLatitude(dto.getLatitude()); @@ -108,14 +120,42 @@ private PositionServiceException parseFeignErrorResponse(FeignException e) { return new PositionServiceException(e.getMessage(), PositionErrorCode.SYSTEM_ERROR, new Object[] {response}); } + private CarDetailsDTO getDefaultCarDetailsByVin(String vin) { + CarDetailsDTO carDetailsDTO = new CarDetailsDTO(); + carDetailsDTO.setVin(vin); + return null; + } + + private List getDefaultCarDetailsList() { + return new ArrayList(); + } + + private List getDefaultStrategicPolygons() { + return new ArrayList(); + } + + private StrategicPolygonDetailedDTO getDefaultStrategicPolygonDetailsById(String polygonId) { + StrategicPolygonDetailedDTO dto = new StrategicPolygonDetailedDTO(); + dto.setId(polygonId); + return dto; + } + + private List getDefaultStrategicPolygonsAndTheirDetailsByName(String name) { + StrategicPolygonDetailedDTO dto = new StrategicPolygonDetailedDTO(); + dto.setName(name); + return Arrays.asList(dto); + } + @Override public Car2StrategicPolygonPositioningVO retrievePositionOfCarAndItsEnclosingPolygonByVin(String vin) throws PositionServiceException { try { Car2StrategicPolygonPositioningVO posVO = new Car2StrategicPolygonPositioningVO(); boolean found = false; - CarDetailsDTO carDetailsDTO = this.carClient.getCarDetailsByVin(vin); + CarDetailsDTO carDetailsDTO = this.carServiceCircuitBreaker.run(() -> this.carClient.getCarDetailsByVin(vin), + throwable -> this.getDefaultCarDetailsByVin(vin)); LOGGER.info("Retrieved car details for vin: {}", vin); - List polygonDTOList = this.polygonClient.getAllPolygons(); + List polygonDTOList = this.polygonServiceCircuitBreaker.run(() -> this.polygonClient.getAllPolygons(), + throwable -> this.getDefaultStrategicPolygons()); if(polygonDTOList != null && !polygonDTOList.isEmpty()) { LOGGER.info("Retrieved strategic polygons: {}", polygonDTOList.size()); for(StrategicPolygonDetailedDTO detailedPolygonDTO : polygonDTOList) { @@ -147,9 +187,13 @@ public Car2StrategicPolygonPositioningVO retrievePositionOfCarAndItsEnclosingPol public StrategicPolygon2CarPositioningVO retrievePositionsOfAllCarsWithinPolygonByPolygonId(String polygonId) throws PositionServiceException { try { StrategicPolygon2CarPositioningVO posVO = new StrategicPolygon2CarPositioningVO(); - StrategicPolygonDetailedDTO polygonDTO = this.polygonClient.getPolygonDetailsById(polygonId); + //StrategicPolygonDetailedDTO polygonDTO = this.polygonClient.getPolygonDetailsById(polygonId); + StrategicPolygonDetailedDTO polygonDTO = this.polygonServiceCircuitBreaker.run(() -> this.polygonClient.getPolygonDetailsById(polygonId), + throwable -> this.getDefaultStrategicPolygonDetailsById(polygonId)); LOGGER.info("Retrieved strategic polygon details for id: {}", polygonId); - List carDetailsDTOList = this.carClient.getAllCarsWithDetails(); + //List carDetailsDTOList = this.carClient.getAllCarsWithDetails(); + List carDetailsDTOList = this.carServiceCircuitBreaker.run(() -> this.carClient.getAllCarsWithDetails(), + throwable -> this.getDefaultCarDetailsList()); if(carDetailsDTOList != null && !carDetailsDTOList.isEmpty()) { LOGGER.info("Retrieved cars: {}", carDetailsDTOList.size()); for(CarDetailsDTO carDetailsDTO : carDetailsDTOList) { @@ -179,9 +223,13 @@ public StrategicPolygon2CarPositioningVO retrievePositionsOfAllCarsWithinPolygon public Set retrievePositionsOfAllCarsWithinPolygonByPolygonName(String name) throws PositionServiceException { Set posVOList = new TreeSet<>(); try { - List polygonDTOList = this.polygonClient.getAllPolygonsByName(name); + //List polygonDTOList = this.polygonClient.getAllPolygonsByName(name); + List polygonDTOList = this.polygonServiceCircuitBreaker.run(() -> this.polygonClient.getAllPolygonsByName(name), + throwable -> this.getDefaultStrategicPolygonsAndTheirDetailsByName(name)); LOGGER.info("Retrieved strategic polygon details for name: {}", name); - List carDetailsDTOList = this.carClient.getAllCarsWithDetails(); + //List carDetailsDTOList = this.carClient.getAllCarsWithDetails(); + List carDetailsDTOList = this.carServiceCircuitBreaker.run(() -> this.carClient.getAllCarsWithDetails(), + throwable -> this.getDefaultCarDetailsList()); if((carDetailsDTOList != null && !carDetailsDTOList.isEmpty()) && (polygonDTOList != null && !polygonDTOList.isEmpty())){ LOGGER.info("Retrieved cars: {}", carDetailsDTOList.size()); @@ -190,7 +238,7 @@ public Set retrievePositionsOfAllCarsWithinPo StrategicPolygon2CarPositioningVO posVO = new StrategicPolygon2CarPositioningVO(); List auxCarDetailsDTOList = new ArrayList<>(); for(CarDetailsDTO carDetailsDTO : carDetailsDTOList) { - if(this.placementService.isCarInsidePolygon(carDetailsDTO, polygonDetailedDTO)) { + if (this.placementService.isCarInsidePolygon(carDetailsDTO, polygonDetailedDTO)) { CarMappedVO carVO = this.carDetailsDTO2VOConverter.convert(carDetailsDTO); posVO.addCar(carVO); atleast1CarFound = true;