Spring Reactive API

Build Status

Build Status

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.

Reactive Mongo

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.

API

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.

Swagger

Para la generación debemos utilizar una versión snapshot de Springfox dado que la última versión 2.x no soporta Webflux.

Probando nuestra API

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": "johndoe@mailserver.org"}}' \
  -H "Content-Type: application/json" \
  http://localhost:8080/customers

# Update customer

curl  \
  -X PUT \
  -d '{"firstName":"John","lastName":"Smith","contactInfo":{"email": "johnsmith@mailserver.org","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