Ejemplo de API REST reactiva basada en Spring Boot 2.x con las siguientes características:
-
Uso de MongoDB
-
Documentada con springfox (aún en la versión snapshot de la 3.x)
-
Uso de RSQL para las búsquedas.
Igual que sus equivalentes no basados en WebFlux simplemente tendremos que declarar las interfaces
extendiendo de ReactiveMongoRepository
:
public interface CustomerRepository extends ReactiveMongoRepository<Customer, String> {
Flux<Customer> findByFirstNameAndLastName(String firstName, String lastName);
}
Hay que tener en cuenta que no podemos utilizar DBRefs en nuestro modelo ya que no están soportados ni parece que vayan a estarlo.
De este modo debemos aplanar el modelo y tendremos que ir a consultar manualmente las relaciones, a diferencia de cuando utilizamos la versión no reactiva de mongo.
Hay dos formas de exponer nuestra API. La primera es similar a la forma no-reactiva, declarando
un @RestController
y anotando los diferentes métodos de nuestra interface:
@RestController
@RequestMapping("/customers")
public class CustomerController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ApiOperation(value = "Find customer by id", response = Customer.class)
Mono<Customer> findOne(@ApiParam(value = "Customer identifier", required = true) @PathVariable String id);
@RequestMapping(method = RequestMethod.GET)
@ApiOperation(value = "Find customers by RSQL", response = Customer.class)
Flux<Customer> find(
@ApiParam(value = "Search default value", required = false, example = "")
@RequestParam(name = "search", defaultValue = "") final String search,
@ApiParam(value = "Page default value", required = false, example = "0")
@RequestParam(name = "page", defaultValue = "0") final int page,
@ApiParam(value = "Size default value", required = false, example = "10")
@RequestParam(name = "size", defaultValue = "10") final int size);
// ...
}
También tenemos la posibilidad de definir un bean que se encargue de mapear los endpoints a nuestros handlers del siguiente modo:
@Configuration
public class PetClinicRouter {
@Bean
RouterFunction<?> routes(PetHandler handler) {
return nest(path("/api/v1/pets"),
route(RequestPredicates.GET("/{id}"), handler::findById)
.andRoute(RequestPredicates.GET(""), handler::findAll)
.andRoute(RequestPredicates.POST(""), handler::save)
);
}
}
Y los métodos que devolvamos en nuestro handler tendrán simplemente que devolver un Mono<ServerResponse>
:
@Component
@AllArgsConstructor
public class PetHandler {
public Mono<ServerResponse> findById(ServerRequest request) { ... }
public Mono<ServerResponse> findAll(ServerRequest request) { ... }
public Mono<ServerResponse> save(ServerRequest request) { ... }
// ...
}
El inconveniente de este método es que actualmente no es compatible con Springfox.
Para la generación debemos utilizar una versión snapshot de Springfox dado que la última versión 2.x no soporta Webflux.
A través de la consola podremos comprobar que nuestra API responde del modo esperado.
A continuación mostramos algunos ejemplos de llamadas.
# Insert customer
curl \
-d '{"firstName":"John","lastName":"Doe","contactInfo":{"email": "[email protected]"}}' \
-H "Content-Type: application/json" \
http://localhost:8080/customers
# Update customer
curl \
-X PUT \
-d '{"firstName":"John","lastName":"Smith","contactInfo":{"email": "[email protected]","phoneNumber": "555 444 888"}}' \
-H "Content-Type: application/json" \
http://localhost:8080/customers/${customerId}
# Find customers using rsql
curl -s "http://localhost:8080/customers?search=firstName==John" | python -m json.tool
# Find customer
curl 'http://localhost:8080/customers/find?firstName=John&lastName=Doe'
# Insert pet
curl \
-d '{"name":"Chinaski","gender":"male","customerId":"5d35affac380b61397788898"}' \
-H "Content-Type: application/json" \
http://localhost:8080/pets
# Find pet by id
curl http://localhost:8080/pets/${petId}
# Find all pets
curl http://localhost:8080/pets