REST API con Spring Boot 2 y Reactive Mongo Repository

Spring Boot 2 está basado en Spring 5, que trajo consigo WebFlux al framework, junto con ello se abrió el espectro de la reactividad en Spring Boot. En esta entrada vamos a ver un ejemplo completo realizando un CRUD desarrollado con Spring Boot 2, Reactive Mongo Repository y MongoDB. Al final podrás ver el código del proyecto, pero necesitarás tener corriendo el servicio de MongoDB, aquí mismo puedes encontrar una guía de como ponerlo operativo usando Docker.

Empezemos por el principio: ¿Qué es la programación reactiva?

La programación reactiva es un paradigma de programación basado en flujos o streams de datos.

En Java particularmente este paradigma se ha visto marcado por dos bibliotecas principales RxJava y Reactor, esta última es la base de todo WebFlux creado por Pivotal.

Flux y Mono

Reactor introduce los tipos Flux Mono como implementaciones de Publisher, los cuales generan series de 0…N y 0…1 elementos respectivamente. La programación reactiva implementa el patrón Publicador/Suscriptor.

  • Mono: Es un tipo de dato para implementaciones reactivas que permite devolver 0 o 1 elemento.
  • Flux: Es el tipo que se emplea para devolver 0 ó muchos elementos en forma de flujo.

CRUD reactivo con Spring Boot 2 y MongoDB

La implementación de aplicaciones reactivas requieren para poder aprovechar al máximo las ventajas de la reactividad de que todo el stack tecnológico que se use soporte dicho paradigma. A día de hoy las bibliotecas de persistencia para manejo de bases de datos SQL/JPA ó (JDBC) no soportan el manejo se streams reactivos, solo está soportado en algunos motores NoSQL y llave-valor, como es el caso de MongoDB.

Dependencias necesarias

Para comenzar a crear nuestro proyecto reactivo con MongoDB debemos incluir las siguientes dependencias como mínimo:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
 </dependency>

Podes ver la lista completa en el POM acá.

Arquitectura del proyecto

El proyecto está basado en arquitectura estándar donde se identifican las siguientes capas:

  • Modelo: Contiene la información de las entidades del negocio (En nuestro caso la clase Message).
  • Repositorio: Implementación del patrón Repository al mismo estilo de JPA: una abstracción de los datos a entidades del negocio.
  • Service: Capa de servicios, donde está la lógica principal.
  • Web: Capa que actúa como controlador, es un proyecto muy sencillo donde solo tendremos el controlador REST para la entidad del modelo Message.

Elementos significativos por capas

En la capa del modelo, nuestra entidad Message

@Document(collection = "message")
public class Message {

    @Id
    private String id = UUID.randomUUID().toString().substring(0, 10);

    private String mailFrom;

    private String mailTo;

    private Date sendedDate = new Date();

    private String subject;

    private String body;

    private String threadId;
    
//resto de setter y getters

Lo interesante acá es que hemos anotado la clase con @Document para indicar que es un Documento JSON y le decimos a MongoDB que lo trate en la colección «message».

En la capa del repositorio, tenemos el MessageRepository

import com.yoandypv.reactivestack.messages.domain.Message;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Flux;

public interface MessageRepository extends ReactiveMongoRepository<Message, String> {
    Flux<Message> findByThreadId(String threadId);
}

Hemos creado la interfaz MessageRepository que implementa el patrón Repository de forma reactiva al extender ReactiveMongoRepository<Message, String>, el segundo parámetro de tipo String es porque la llave primaria de Message es de tipo String, debe haber correspondencia al igual sucede en JPA.

Hemos añadido adicionalmente, es el método findByThread, solo para ilustrar que es posible hacer consultas basadas en la convención de nombres, esto agiliza mucho los tiempos de desarrollo. ReactiveMongoRepository sigue los principios de Spring Data.

En la capa de Servicios hemos creado una interfaz llamada MessageService y luego la hemos implementado en MessageServiceImpl, veamos la implementación.

import com.yoandypv.reactivestack.messages.domain.Message;
import com.yoandypv.reactivestack.messages.repository.MessageRepository;
import com.yoandypv.reactivestack.messages.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class MessageServiceImpl implements MessageService {

    @Autowired
    private MessageRepository messageRepository;

    @Override
    public Mono<Message> save(Message message) {
        return this.messageRepository.save(message);
    }

    @Override
    public Mono<Message> delete(String id) {
        return this.messageRepository
                .findById(id)
                .flatMap(p -> this.messageRepository.deleteById(p.getId()).thenReturn(p));

    }

    @Override
    public Mono<Message> update(String id, Message message) {
        return this.messageRepository.findById(id)
                .flatMap(message1 -> {
                    message.setId(id);
                    return save(message);
                })
                .switchIfEmpty(Mono.empty());
    }

    @Override
    public Flux<Message> findByThreadId(String threadId) {
        return this.messageRepository.findByThreadId(threadId);
    }


    @Override
    public Flux<Message> findAll() {
        return this.messageRepository.findAll();
    }

    @Override
    public Mono<Message> findById(String id) {
        return this.messageRepository.findById(id);
    }
}

Como en todos los proyectos Spring Boot hemos usado Autowired para inyectar nuestro repositorio y poder acceder a las operaciones del CRUD que nos facilita ReactiveMongoRepository, que son muy similares a las de los proyectos JPA, pero emplean Mono y Flux para los flujos.

En la capa web tenemos nuestro controlador REST.

@RestController
public class MessageResource {

    @Autowired
    private MessageService messageService;

    @PostMapping("/messages")
    @ResponseStatus(HttpStatus.CREATED)
    private Mono<Message> save(@RequestBody Message message) {
        return this.messageService.save(message);
    }

    @DeleteMapping("/messages/{id}")
    private Mono<ResponseEntity<Message>> delete(@PathVariable("id") String id) {
        return this.messageService.delete(id)
                .flatMap(message -> Mono.just(ResponseEntity.ok(message)))
                .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));

    }

    @PutMapping("/messages/{id}")
    private Mono<ResponseEntity<Message>> update(@PathVariable("id") String id, @RequestBody Message message) {
        return this.messageService.update(id, message)
                .flatMap(message1 -> Mono.just(ResponseEntity.ok(message1)))
                .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));

    }

    @GetMapping("/messages/{threadId}/bythreadId")
    private Flux<Message> findAllByThreadId(@PathVariable("threadId") String threadId) {
        return this.messageService.findByThreadId(threadId);
    }

    @GetMapping(value = "/messages")
    private Flux<Message> findAll() {
        return this.messageService.findAll();
    }

}

Aquí es importante destacar que cambia un poco el manejo para poder retornar resultados HTTP acorde con las respuestas estándares que normalmente se emplean.

Anteriormente se devolvía un ResponseEntity<T> del tipo de la respuesta, ahora para mantener la estructura del stream podemos devolver un Mono<ResponseEntity<<T>> ó Flux<ResponseEntity<<T>> en dependencia de si vamos a devolver 0..1 ó 0..n elementos.

La documentación oficial del starter reactivo de MongoDB puede complementar lo escrito acá. Adicionalmente el código completo del artículo puede ser descargado libremente desde el repositorio en Github.

Si tienes alguna duda o criterio, déjanos tu comentario.

4 comentarios en «REST API con Spring Boot 2 y Reactive Mongo Repository»

  • el 30 de agosto de 2019 a las 22:18
    Enlace permanente

    I’ve been surfing online more than 2 hours today, yet I never
    found any interesting article like yours. It is
    pretty worth enough for me. In my view, if all website owners and bloggers made good content as you did, the net will be much more
    useful than ever before. Howdy, i read your blog from time to
    time and i own a similar one and i was just wondering if you get a lot of spam responses?
    If so how do you protect against it, any plugin or anything you can suggest?
    I get so much lately it’s driving me insane so any
    support is very much appreciated. Hi, I do believe
    this is a great web site. I stumbledupon it 😉 I am going to return once again since
    i have book marked it. Money and freedom is the greatest way to
    change, may you be rich and continue to help other people.

    http://foxnews.net

    Respuesta
  • el 22 de septiembre de 2019 a las 20:07
    Enlace permanente

    Magnificent goods from you, man. I’ve understand your stuff previous to and you’re just extremely wonderful.
    I really like what you’ve acquired here, really like what you are saying and
    the way in which you say it. You make it enjoyable and you still
    take care of to keep it smart. I can’t wait to read much more from you.
    This is really a tremendous website.

    Respuesta
  • Pingback: Homepage

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

SACAViX Tech