From d6cd5625f3cff877736f676f927080874804482e Mon Sep 17 00:00:00 2001 From: Gustavo Martin Morcuende Date: Sat, 10 Dec 2016 02:37:10 +0100 Subject: [PATCH] RxJava REST example --- .../rest/business/service/RxJavaBusinessLogic.java | 18 ++++ .../impl/CompletablefutureBusinessLogicImpl.java | 9 ++ .../service/impl/RxJavaBusinessLogicImpl.java | 47 ++++++++++ .../rest/controller/RxJavaCarController.java | 102 ++++++--------------- .../rest/controller/adapters/RxJavaAdapter.java | 42 +++++++++ .../src/main/webapp/WEB-INF/web.xml | 2 +- 6 files changed, 144 insertions(+), 76 deletions(-) create mode 100644 SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/RxJavaBusinessLogic.java create mode 100644 SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/RxJavaBusinessLogicImpl.java create mode 100644 SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/adapters/RxJavaAdapter.java diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/RxJavaBusinessLogic.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/RxJavaBusinessLogic.java new file mode 100644 index 0000000..7d49402 --- /dev/null +++ b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/RxJavaBusinessLogic.java @@ -0,0 +1,18 @@ +package de.spring.webservices.rest.business.service; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import de.spring.webservices.domain.Car; +import rx.Observable; + + +public interface RxJavaBusinessLogic { + + public Observable> findAll(Pageable pageRequest); + + public Observable findById(long id); + + public Observable create(Car resource); + +} diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/CompletablefutureBusinessLogicImpl.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/CompletablefutureBusinessLogicImpl.java index 0b5af2f..001d3a7 100644 --- a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/CompletablefutureBusinessLogicImpl.java +++ b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/CompletablefutureBusinessLogicImpl.java @@ -12,6 +12,15 @@ import de.spring.webservices.domain.Car; import de.spring.webservices.rest.business.service.AwesomeBusinessLogic; import de.spring.webservices.rest.business.service.CompletableFutureBusinessLogic; +/** + * + * + * TODO: WHAT ABOUT EXCEPTIONS FROM awesomeBusinessLogic? RuntimeExceptions for example + * I guess they will be caught by my adapter in controller layer but I must try it. + * + */ + + @Service("completableFutureBusinessLogic") public class CompletablefutureBusinessLogicImpl implements CompletableFutureBusinessLogic { private final AwesomeBusinessLogic awesomeBusinessLogic; diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/RxJavaBusinessLogicImpl.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/RxJavaBusinessLogicImpl.java new file mode 100644 index 0000000..5eec938 --- /dev/null +++ b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/business/service/impl/RxJavaBusinessLogicImpl.java @@ -0,0 +1,47 @@ +package de.spring.webservices.rest.business.service.impl; + +import javax.inject.Inject; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import de.spring.webservices.domain.Car; +import de.spring.webservices.rest.business.service.AwesomeBusinessLogic; +import de.spring.webservices.rest.business.service.RxJavaBusinessLogic; +import rx.Observable; + +/** + * + * + * TODO: WHAT ABOUT EXCEPTIONS FROM awesomeBusinessLogic? RuntimeExceptions for example + * I guess they will be caught by my adapter in controller layer but I must try it. + * + */ + + +@Service("rxJavaBusinessLogic") +public class RxJavaBusinessLogicImpl implements RxJavaBusinessLogic { + private final AwesomeBusinessLogic awesomeBusinessLogic; + + @Inject + public RxJavaBusinessLogicImpl(AwesomeBusinessLogic awesomeBusinessLogic) { + this.awesomeBusinessLogic = awesomeBusinessLogic; + } + + @Override + public Observable> findAll(Pageable pageRequest) { + return Observable.create(observer -> observer.onNext( awesomeBusinessLogic.findAll(pageRequest))); + + } + + @Override + public Observable findById(long id) { + return Observable.create(observer -> observer.onNext( awesomeBusinessLogic.findById(id))); + } + + @Override + public Observable create(Car car) { + return Observable.create(observer -> observer.onNext(awesomeBusinessLogic.create(car))); + } +} diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/RxJavaCarController.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/RxJavaCarController.java index a6d3b08..1426d0d 100644 --- a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/RxJavaCarController.java +++ b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/RxJavaCarController.java @@ -1,7 +1,8 @@ package de.spring.webservices.rest.controller; +import static de.spring.webservices.rest.controller.adapters.RxJavaAdapter.deferredAdapter; + import java.util.Map; -import java.util.concurrent.CompletableFuture; import javax.inject.Inject; @@ -24,7 +25,8 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import de.spring.webservices.domain.Car; -import de.spring.webservices.rest.business.service.AwesomeBusinessLogic; +import de.spring.webservices.rest.business.service.RxJavaBusinessLogic; +import rx.Observable; @RestController @RequestMapping("/api/rxjava/cars/") @@ -32,45 +34,19 @@ public class RxJavaCarController { private static final Logger LOGGER = LoggerFactory.getLogger(RxJavaCarController.class); private static final int PAGE = 2; private static final int PAGE_SIZE = 10; - - // With no value, we depend on the Tomcat/Jboss/Jetty/etc timeout value for asynchronous requests. - // Spring will answer after 60 secs with an empty response (by default) and HTTP 503 status (by default) when timeout. - private static final long ASYNC_TIMEOUT = 60000; /* milliseconds */ - - /** - * - * WHEN EXCEPTION IN setErrorResult, Spring WILL TRIGGER THE Spring Exception Handler AS YOU KNOW IT (I HOPE) - * SO, YOU COULD HOOK UP THE HANDLER AND RETURN YOUR CUSTOM MESSAGESS (as usual) - * - */ - - private final AwesomeBusinessLogic awesomeBusinessLogic; + + private final RxJavaBusinessLogic rxJavaBusinessLogic; @Inject - public RxJavaCarController(AwesomeBusinessLogic awesomeBusinessLogic) { - this.awesomeBusinessLogic = awesomeBusinessLogic; + public RxJavaCarController(RxJavaBusinessLogic completableFutureBusinessLogic) { + this.rxJavaBusinessLogic = completableFutureBusinessLogic; } @RequestMapping(produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, method = RequestMethod.GET) @ResponseStatus(HttpStatus.OK) public DeferredResult> cars() { - - // THIS CODE (I GUESS) SHOULD BE LOCATED IN Service layer. Anyhow this is just an example. - DeferredResult> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT); - CompletableFuture - .supplyAsync(() -> awesomeBusinessLogic.findAll(new PageRequest(PAGE, PAGE_SIZE))) - .thenAcceptAsync(car -> deferredResult.setResult(car)) - .exceptionally(exception -> { - LOGGER.error("findAll error: ", exception); - - // DO NOT FORGET THE EXCEPTIONS. - // It will trigger the Spring Exception Handler as you know it :) - deferredResult.setErrorResult(exception); - - return null; - }); - - return deferredResult; + + return deferredAdapter(rxJavaBusinessLogic.findAll(new PageRequest(PAGE, PAGE_SIZE))); } @RequestMapping(value = "{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE, method = RequestMethod.GET) @@ -97,24 +73,9 @@ public class RxJavaCarController { LOGGER.info(wheel); } } - - // THIS CODE (I GUESS) SHOULD BE LOCATED IN Service layer. Anyhow this is just an example. - DeferredResult deferredResult = new DeferredResult<>(ASYNC_TIMEOUT); - CompletableFuture - .supplyAsync(() -> awesomeBusinessLogic.findById(id)) - .thenAcceptAsync(car -> deferredResult.setResult(car)) - .exceptionally(exception -> { - - LOGGER.error("findById error: ", exception); - - // DO NOT FORGET THE EXCEPTIONS. - // It will trigger the Spring Exception Handler as you know it :) - deferredResult.setErrorResult(exception); - - return null; - }); - - return deferredResult; + + return deferredAdapter(rxJavaBusinessLogic.findById(id)); + } @RequestMapping(consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, @@ -122,29 +83,20 @@ public class RxJavaCarController { @ResponseStatus(HttpStatus.CREATED) public DeferredResult> create(@RequestBody Car car) { - // THIS CODE (I GUESS) SHOULD BE LOCATED IN Service layer. Anyhow this is just an example. - DeferredResult> deferredResult = new DeferredResult<>(ASYNC_TIMEOUT); - CompletableFuture - .supplyAsync(() -> { - Car createdCar = awesomeBusinessLogic.create(car); - - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.LOCATION, "/api/cars/" + createdCar.getId()); - return new ResponseEntity<>(createdCar, headers, HttpStatus.CREATED); - }) - .thenAcceptAsync(response -> deferredResult.setResult(response)) - .exceptionally(exception -> { - - LOGGER.error("create error: ", exception); - - // DO NOT FORGET THE EXCEPTIONS. - // It will trigger the Spring Exception Handler as you know it :) - deferredResult.setErrorResult(exception); - - return null; - }); - - return deferredResult; + return deferredAdapter(createAsync(car)); } + + private Observable> createAsync(Car car) { + + return rxJavaBusinessLogic + .create(car) + .map(this::createResponseCar); + } + + private ResponseEntity createResponseCar(Car car) { + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.LOCATION, "/api/cars/" + car.getId()); + return new ResponseEntity<>(car, headers, HttpStatus.CREATED); + } } diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/adapters/RxJavaAdapter.java b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/adapters/RxJavaAdapter.java new file mode 100644 index 0000000..ca52b11 --- /dev/null +++ b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/java/de/spring/webservices/rest/controller/adapters/RxJavaAdapter.java @@ -0,0 +1,42 @@ +package de.spring.webservices.rest.controller.adapters; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.context.request.async.DeferredResult; + +import rx.Observable; +import rx.schedulers.Schedulers; + +public class RxJavaAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger(RxJavaAdapter.class); + + /** + * + * WHEN EXCEPTION IN setErrorResult, Spring WILL TRIGGER THE Spring Exception Handler AS YOU KNOW IT (I HOPE) + * SO, YOU COULD HOOK UP THE HANDLER AND RETURN YOUR CUSTOM MESSAGESS (as usual) + * + */ + + // With no value, we depend on the Tomcat/Jboss/Jetty/etc timeout value for asynchronous requests. + // Spring will answer after 60 secs with an empty response (by default) and HTTP 503 status (by default) when timeout. + private static final long ASYNC_TIMEOUT = 60000; /* milliseconds */ + + + @FunctionalInterface + public interface DeferredCall { + + public T doCall(); + } + + public static final DeferredResult deferredAdapter(Observable observable) { + + DeferredResult deferredResult = new DeferredResult<>(ASYNC_TIMEOUT); + + observable + .subscribeOn(Schedulers.io()) + .subscribe(deferredResult::setResult, exception -> LOGGER.error("error: ", exception)); + + return deferredResult; + } + +} diff --git a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/webapp/WEB-INF/web.xml b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/webapp/WEB-INF/web.xml index 6c9c1d0..a1eda07 100644 --- a/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/webapp/WEB-INF/web.xml +++ b/SpringJava/RxJava/web-services-spring-rxjava-server/src/main/webapp/WEB-INF/web.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> - Spring REST Services: example + Spring Reactive REST Services: example -- 2.1.4