===== Creación de APIs reactivas: WebFlux ===== {{webflux.png?100 }} ==== Introducción a WebFlux ==== https://dzone.com/articles/build-reactive-rest-apis-with-spring-webflux ==== Programación reactiva con WebFlux ==== @Data @AllArgsConstructor @NoArgsConstructor @Document("products") public class Product implements Serializable { @Id private String id; @Field private String name; } public class ProductValidator implements Validator { @Override public boolean supports(Class clazz) { return Product.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "field.required"); } } @Data @AllArgsConstructor @NoArgsConstructor public class ErrorResponse { private int code; private String message; } @Repository public interface ProductRepository extends ReactiveMongoRepository { } @Service public class ProductService { @Autowired private ProductRepository productRepository; public Flux getAllProducts() { return productRepository.findAll(); } public Mono getProduct(String id) { return productRepository.findById(id); } public Mono save(Product product) { Product newProduct = new Product(); newProduct.setName(product.getName()); return productRepository.save(newProduct); } public Mono deleteProduct(String id) { return productRepository.deleteById(id); } public Mono update(Mono product) { return product .flatMap((p) -> productRepository.findById(p.getId()) .flatMap(product1 -> { product1.setName(p.getName()); return productRepository.save(product1); })); } } @Component public class ProductHandler { @Autowired private ProductService productService; static Mono notFound = ServerResponse.notFound().build(); private final Validator validator = new ProductValidator(); public Mono getAllProducts(ServerRequest serverRequest) { return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(productService.getAllProducts(), Product.class); } public Mono getProduct(ServerRequest serverRequest) { String id = serverRequest.pathVariable("id"); return productService.getProduct(id) .flatMap(p -> ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(Mono.just(p), Product.class)) .switchIfEmpty(ServerResponse.status(HttpStatus.NOT_FOUND) .contentType(MediaType.APPLICATION_JSON) .body(Mono.just(new ErrorResponse(404, "Product not found")), ErrorResponse.class)); } public Mono createProduct(ServerRequest serverRequest) { Mono productToSave = serverRequest.bodyToMono(Product.class) .doOnNext(this::validate); return productToSave.flatMap(product -> ServerResponse.status(HttpStatus.CREATED) .contentType(MediaType.APPLICATION_JSON) .body(productService.save(product), Product.class)); } private void validate(Product product) { Errors errors = new BeanPropertyBindingResult(product, "product"); validator.validate(product, errors); if (errors.hasErrors()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errors.getAllErrors().toString()); } } public Mono deleteProduct(ServerRequest serverRequest) { String id = serverRequest.pathVariable("id"); return productService.deleteProduct(id) .flatMap(product -> ServerResponse.noContent().build()); } public Mono updateProduct(ServerRequest serverRequest) { return productService.update(serverRequest.bodyToMono(Product.class)).flatMap(product -> ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(fromObject(product))) .switchIfEmpty(notFound); } } @Configuration public class ProductRouter { @Bean public RouterFunction productsRoute(ProductHandler productHandler){ return RouterFunctions .route(GET("/products").and(accept(MediaType.APPLICATION_JSON)), productHandler::getAllProducts) .andRoute(GET("/product/{id}").and(accept(MediaType.APPLICATION_JSON)), productHandler::getProduct) .andRoute(POST("/products").and(accept(MediaType.APPLICATION_JSON)), productHandler::createProduct) .andRoute(DELETE("/product/{id}").and(accept(MediaType.APPLICATION_JSON)), productHandler::deleteProduct) .andRoute(PUT("/product/{id}").and(accept(MediaType.APPLICATION_JSON)), productHandler::updateProduct); } } @SpringBootApplication public class ReactiveApi { public static void main(String[] args) { SpringApplication.run(ReactiveApi.class, args); } } server.port=8080 spring.data.mongodb.host=localhost spring.data.mongodb.port=27017 spring.data.mongodb.database=reactive-api server.error.include-message=always ==== Consumir una API reactiva con WebClient ==== @Data @NoArgsConstructor @ToString public class Product { private String id; private String name; } WebClient webClient = WebClient.create("http://localhost:8080"); Flux productsFlux = webClient.get() .uri("/products") .retrieve() .bodyToFlux(Product.class); productsFlux.doOnError((System.out::println)) .subscribeOn(Schedulers.fromExecutor(Executors.newCachedThreadPool())) .doOnComplete(() -> System.out.println("Terminado")) .subscribe((product) -> { System.out.println("Haciendo algo con " + product.getId() + " . . ."); }); productsFlux.doOnError((System.out::println)) .subscribeOn(Schedulers.fromExecutor(Executors.newCachedThreadPool())) .doOnComplete(() -> System.out.println("Terminado")) .subscribe((product) -> System.out.println("Consumidor 2: " + product.getName())); ---- (c) 2022-{{date>%Y}} Santiago Faci