===== 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