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
y 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.
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
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.
Saved as a favorite, I like your site! https://cavm-ombc.ca