En este post aprenderemos a crear un microservicio spring boot que utilizará Spring Data MongoDB para crear una aplicación que almacenará y recuperará datos de MongoDB, una de las bases de datos NoSQL orientada a documentos más populares.
Instalación y ejecución de MongoDB
Si no dispones de una instalación previa de MongoDB, en este link tienes información de cómo hacerlo.
Para instalar por ejemplo MongoDB en Mac OS X, ejecuta el siguiente comando:
$ brew install mongodb
Crea el directorio donde el proceso mongod escribirá los datos cuando MongoDB esté levantado. Es importarte otorgar permisos de lectura y escritura al usuario que ejecute el proceso:
$ mkdir -p /data/db $ sudo chmod 777 /data/db
Por último levanta el servidor de MongoDB:
$ mongod
Por defecto las peticiones se escuchan en el puerto 27017
Antes de empezar
Si te resulta útil, puedes descargarte el el código fuente de este ejercicio desde mi proyecto de GitHub
La configuración del fichero pom.xml del proyecto es la siguiente:
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.robertocrespo.microservices</groupId> <artifactId>users</artifactId> <version>1.0.1-RELEASE</version> <packaging>jar</packaging> <name>users</name> <description>This project is in charge of users service</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <springfox.version>2.4.0</springfox.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> <version>1.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${springfox.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${springfox.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <plugin> <artifactId>maven-release-plugin</artifactId> <version>2.5</version> <dependencies> <dependency> <groupId>org.apache.maven.scm</groupId> <artifactId>maven-scm-provider-gitexe</artifactId> <version>1.3</version> </dependency> </dependencies> </plugin> </plugins> </build>
Implementación del microservicio
1 – Crear objeto de dominio
Crearemos un POJO muy sencillo para representar un User, que será el que almacenemos en MongoDB
package net.robertocrespo.microservices.users.model; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document import java.io.Serializable; import javax.validation.constraints.NotNull; @Document(collection = "users") @JsonPropertyOrder({"userId", "name"}) public class User implements Serializable{ private static final long serialVersionUID = -7788619177798333712L; @Id @NotNull private String userId; @NotNull private String name; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
Utilizamos la anotación @Document para definir un nombre de una colección cuando el objeto se guarde en MongoDB. En este caso, cuando el objeto “user” se guarde, se hará dentro de la colección “users”.
La anotación de Jackson @JsonPropertyOrder nos permite especificar el orden en que los campos del objeto java deberían ser serializados en JSON.
2 – Crear User repository
Lo primero de todo será crear el interface del repositorio que permita realizar varias operaciones CRUD sobre el objeto User
package net.robertocrespo.microservices.users.repository; import net.robertocrespo.microservices.users.model.User; public interface UserRepository{ Optional<List<User>> findAll(); public User saveUser(User user); public void updateUser(User user); public void deleteUser(String userId); }
Y a continuación la implementación del interface:
package net.robertocrespo.microservices.users.repository.impl; import net.robertocrespo.microservices.users.repository.UserRepository; import net.robertocrespo.microservices.users.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.stereotype.Repository; import org.springframework.util.Assert; import java.util.List; import java.util.Optional; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; @Repository public class UserRepositoryImpl implements UserRepository{ private final MongoOperations mongoOperations; @Autowired @Autowired public UserRepositoryImpl(MongoOperations mongoOperations) { Assert.notNull(mongoOperations); this.mongoOperations = mongoOperations; } //Find all users public Optional&lt;List&lt;User&gt;&gt; findAll() { List&lt;User&gt; users = this.mongoOperations.find(new Query(), User.class); Optional&lt;List&lt;User&gt;&gt; optionalUsers = Optional.ofNullable(users); return optionalUsers; } public Optional&lt;User&gt; findOne(String userId) { User d = this.mongoOperations.findOne(new Query(Criteria.where("userId").is(userId)), User.class); Optional&lt;User&gt; user = Optional.ofNullable(d); return user; } public User saveUser(User user) { this.mongoOperations.save(user); return findOne(user.getUserId()).get(); } public void updateUser(User user) { this.mongoOperations.save(user); } public void deleteUser(String userId) { this.mongoOperations.findAndRemove(new Query(Criteria.where("userId").is(userId)), User.class); } }
3 – Implementar User Service
Lo siguiente que haremos será crear el servicio (interface + implementación) que se conecte al repositorio de usuarios del paso anterior:
package net.robertocrespo.microservices.users.service; import net.robertocrespo.microservices.users.model.User; import java.util.List; public interface UserService { List&amp;lt;User&amp;gt; findAll(); User findByUserId(String userId); User saveUser(User user); void updateUser(User user); void deleteUser(String userId); }
A continuación la implementación de la interface UserService:
package net.robertocrespo.microservices.users.service.impl; import net.robertocrespo.microservices.users.exception.UserNotFoundException; import net.robertocrespo.microservices.users.model.User; import net.robertocrespo.microservices.users.repository.UserRepository; import net.robertocrespo.microservices.users.service.UserService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service("userService") @Transactional public class UserServiceImpl implements UserService { private static final Log log = LogFactory.getLog(UserServiceImpl.class); private UserRepository userRepository; @Autowired public UserServiceImpl(UserRepository userRepository){ this.userRepository = userRepository; } public User findByUserId(String userId) { Optional&amp;amp;lt;User&amp;amp;gt; user = userRepository.findOne(userId); if (user.isPresent()) { log.debug(String.format("Read userId '{}'", userId)); return user.get(); }else throw new UserNotFoundException(userId); } public List&amp;amp;lt;User&amp;amp;gt; findAll() { Optional&amp;amp;lt;List&amp;amp;lt;User&amp;amp;gt;&amp;amp;gt; user = userRepository.findAll(); return user.get(); } public User saveUser(User user) { return userRepository.saveUser(user); } public void updateUser(User user) { userRepository.updateUser(user); } public void deleteUser(String userId) { userRepository.deleteUser(userId); } }
En el caso de no localizar un usuario por su userId, se lanzará la excepción UserNorFoundException
package net.robertocrespo.microservices.users.exception; import org.springframework.core.NestedRuntimeException; public class UserNotFoundException extends NestedRuntimeException { public UserNotFoundException(String userId) { super(String.format("User with Id '%s' not founded", userId)); } }
4 – Definir Controller
A continuación la implementación del UserController:
package net.robertocrespo.microservices.users.controller; import net.robertocrespo.microservices.users.exception.UserNotFoundException; import net.robertocrespo.microservices.users.model.User; import net.robertocrespo.microservices.users.service.UserService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import javax.validation.Valid; @RestController @RequestMapping("users") public class UsersController { private static final Log log = LogFactory.getLog(UsersController.class); private final UserService usersService; private User user; @Autowired public UsersController(UserService usersService) { this.usersService = usersService; } @RequestMapping(value="/{userId}",method = RequestMethod.GET) @ApiOperation(value = "Find an user", notes = "Return a user by Id" ) public ResponseEntity&amp;amp;amp;amp;lt;User&amp;amp;amp;amp;gt; userById(@PathVariable String userId) throws UserNotFoundException{ log.info("Get userById"); try{ user = usersService.findByUserId(userId); }catch(UserNotFoundException e){ user = null; } return ResponseEntity.ok(user); } @RequestMapping(method = RequestMethod.GET) public ResponseEntity&amp;amp;amp;amp;lt;List&amp;amp;amp;amp;lt;User&amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; userById(){ log.info("Get allUsers"); return ResponseEntity.ok(usersService.findAll()); } @RequestMapping(method = RequestMethod.GET) @ApiOperation(value = "Find all user", notes = "Return all users" ) public ResponseEntity&amp;amp;amp;amp;lt;List&amp;amp;amp;amp;lt;User&amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt; userById(){ log.info("Get allUsers"); return ResponseEntity.ok(usersService.findAll()); } @RequestMapping(value="/{userId}",method = RequestMethod.DELETE) @ApiOperation(value = "Delete an user", notes = "Delete a user by Id") public ResponseEntity&amp;amp;amp;amp;lt;Void&amp;amp;amp;amp;gt; deleteUser(@PathVariable String userId){ log.info("Delete user " + userId); usersService.deleteUser(userId); return ResponseEntity.noContent().build(); } @RequestMapping(method=RequestMethod.POST) @ApiOperation(value = "Create an user", notes = "Create a new user") public ResponseEntity&amp;amp;amp;amp;lt;User&amp;amp;amp;amp;gt; saveUser(@RequestBody @Valid User user){ log.info("Save new user"); return ResponseEntity.ok(usersService.saveUser(user)); } }
5 – Crear aplicación ejecutable
Por último, solo nos queda crear la clase que ejecute la aplicación Spring Boot:
package net.robertocrespo.microservices.users; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication public class UsersApplication { public static void main(String[] args) { SpringApplication.run(UsersApplication.class, args); } }
Por defecto, cuando inicias una aplicación spring boot , se busca un fichero llamado application.properties o application.yml para acceder a su configuración, el cual deberá estar ubicado en la carpeta resources de nuestro proyecto. Su configuración es la siguiente:
# Spring properties spring: data: mongodb: host: localhost port: 27017 uri: mongodb://localhost/test # HTTP Server server: port: 4444 # HTTP (Tomcat) port
Prueba que el microservicio funciona correctamente
Probaremos el servicio por medio del interface RESTful que ofrece. Para facilitarte esta tarea, te recomiendo utilizar un cliente que te permita realizar peticiones HTTP, como Postman o Advance REST Client
Antes de nada, recuerda tener levantado el demonio de MongoDB mediante mongod.
A continuación te indico la configuración que debes establecer en las peticiones HTTP para ejecutar las distintas operaciones CRUD del microservicio:
- Crear nuevo usuario:
- POST: https://localhost:4444/users
- Header
- Content-Type: application/json
- Accept: application/json
- Body:
- {«userId»:»1″,»name»:»Rob»}
- {«userId»:»2″,»name»:»Peter»}
- {«userId»:»1″,»name»:»Rob»}
- Obtener un usuario por userId
- GET: https://localhost:4444/users/1
- Nota: userId=1
- GET: https://localhost:4444/users/1
- Obtener todos los usuario
- Modificar usuario:
- PUT: https://localhost:4444/users
- Header
- Content-Type: application/json
- Accept: application/json
- Body:
- {«userId»:»1″,»name»:»John»}
- Eliminar un usuario por userId
- DELETE: https://localhost:4444/users/1
- Nota: userId=1
- DELETE: https://localhost:4444/users/1
En el siguiente post aprenderemos cómo documentar y probar este API REST con Swagger
7 Responses
Hola gracias por el post, me puedes recomendar algún post o sitio que muestre como configurar el acceso a una base de datos en MongoDB Atlas, no he podido encontrar un ejemplo que use solo la variable spring.data.mongodb.uri en application.properties, todos los ejemplos usan una base de datos de MongoDB local, gracias de ante mano.
Muy bueno este post, pero me gustaría que hiciesen unos post con Oracle actualizando a través de procedimientos y utilizando oAuth2 como servicio de control de las api a traves jwt.
Donde yo trabajo no nos permites interacturar directamente con las tablas de datos. Para todos los casos se deben utilizar procedimienos y funciones, como una medida de protección. Y tengo entendido que en otras empresas sucede lo mismo.
Desde ya agradezco estos post porque nos enseñan muchísimo.
Gracias por el post, estoy comenzado con Mongodb y me ha servido de mucho tú post, sobre todo a la hora de integrarlo con Spring.
Un saludo.
Buenas tardes quisiera me colabore como implementar una consulta aleatoria mongodb y sprinboot la realizo de esta forma:
@Query(«{ $sample: { size: 1 } }»)
List findAllBySize();
me salen el siguiente error
«timestamp»: «2019-01-24T19:00:53.968+0000»,
«status»: 500,
«error»: «Internal Server Error»,
«message»: «Query failed with error code 2 and error message ‘unknown top level operator: $sample’ on server localhost:27017; nested exception is com.mongodb.MongoQueryException: Query failed with error code 2 and error message ‘unknown top level operator: $sample’ on server localhost:27017»,
«path»: «/myapplication/api/home»
Buenas tardes quisiera me colabore con este error que tengo
requiero realizar una consulta personaliza en mongo pero al momento no me funciona
Hola Roberto estan geniales tus post y los estoy siguiendo, además se lo estoy recomendando a mis colegas, te quiero hacer una pregunta en una arquitectura orientada a MS, Mongo DB es utilizada para almacenar los usuarios o para que es comunemte utilzada?. Saludos y gracias de antemano
Hola!
Te agradezco mucho tus comentarios. Como seguramente ya sabes, MongoDB es una base de datos NoSQL orientada a documentos. Como cualquier base de datos NoSQL, son útiles para guardar principalmente información no estructurada, es decir, estructuras de datos que pueden variar en el tiempo. En las bases de datos relaciones, asumir esos cambios es mas costoso que las BBDD NoSQL. Te recomiendo consultar en internet información relacionada sobre BBDD relaciones vs NoSQL. Para los ejemplos que estamos haciendo, podíamos haber trabajado perfectamente con una base de datos relacionar como MySQL o PostgreSQL.
Saludos