Introducción a Spring Boot con Spring Data Mongo

Hola a todos ¡Nos apresuramos a felicitar a los estudiantes por sus vacaciones profesionales e informarles que en febrero comenzaremos el curso "Desarrollador en el Marco de Primavera" ! Este será el tema de la publicación de hoy.

La Liga de la Justicia está en peligro, y solo Alfred puede salvar a todos, con el nuevo sistema de gestión con Spring Boot, Spring Data y MongoDB.

Para la Liga de la Justicia, los tiempos oscuros han llegado cuando el impresionante Darkside decidió esclavizar a la humanidad. Batman y Wonder Woman comenzaron a buscar miembros de la Liga, y solo falta un punto importante: el sistema de gestión adecuado para los miembros de la Liga de la Justicia.



No hay tiempo suficiente para crear un proyecto voluminoso desde cero, por lo que Batman pasa esta difícil tarea a su querido Alfred (Robin es demasiado impredecible), quien recuerda algo llamado Spring Boot, que lo ayudará a no perder el tiempo resolviendo pequeños matices de configuración del proyecto y cambiando rápidamente para escribir el código de la aplicación.
Entonces, nuestro querido Alfred comienza a usar Spring Boot para crear rápidamente un sistema de administración de membresía de la Liga de la Justicia. Como mínimo, sus partes de back-end, ya que Batman trabaja directamente con la API REST.

Hay muchas formas convenientes de configurar las aplicaciones Spring Boot. En este artículo, nos centramos en el método tradicional de descargar un paquete (Spring CLI) y configurarlo desde cero en Ubuntu. Spring también admite el empaquetado del proyecto en línea con su herramienta . Descargue la última versión estable aquí . En este artículo, estoy usando la versión 1.3.0.M1.

Después de desempaquetar el archivo descargado, para empezar, configure los siguientes parámetros en el perfil:

SPRING_BOOT_HOME=<extracted path>/spring-1.3.0.M1 PATH=$SPRING_BOOT_HOME/bin:$PATH 

Luego agregue lo siguiente al archivo bashrc:

 <extracted-path>/spring-1.3.0.M1/shell-completion/bash/spring 

Esto agregará autocompletado a la línea de comando cuando trabaje con spring-cli para crear aplicaciones Spring Boot. Recuerde que para confirmar los cambios necesita "fuente" el perfil y los archivos "bashrc".

Este artículo utiliza la siguiente pila de tecnología:

  • RESTO de primavera
  • Datos de primavera
  • MongoDB.

Comenzamos creando una plantilla de proyecto de aplicación ejecutando el siguiente comando. Tenga en cuenta que un proyecto de muestra se puede descargar desde mi repositorio de GitHub aquí .

 spring init -dweb,data-mongodb,flapdoodle-mongo --groupId com.justiceleague --artifactId justiceleaguemodule --build maven justiceleaguesystem 

Esto generará un proyecto maven con Spring MVC y Spring Data con MongoDB integrado.
Por defecto, spring-cli crea un proyecto llamado "Demo". Por lo tanto, debemos cambiar el nombre de la clase de aplicación generada correspondiente. Si utilizó las fuentes de mi repositorio GitHub mencionado anteriormente, puede omitir este paso.
Con Spring Boot, ejecutar la aplicación es tan simple como ejecutar el archivo JAR creado por el proyecto. Lo que simplemente llama a la clase de aplicación anotada por @SpringBootApplication para cargar Spring. Veamos cómo se ve:

 package com.justiceleague.justiceleaguemodule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** *   spring boot,   -    *   * * @author dinuka * */ @SpringBootApplication public class JusticeLeagueManagementApplication { public static void main(String[] args) { SpringApplication.run(JusticeLeagueManagementApplication.class, args); } } 

Luego pasamos a las clases de dominio, donde se usan Spring Data junto con MongoDB para determinar el nivel de datos. La clase de dominio es la siguiente:

 package com.justiceleague.justiceleaguemodule.domain; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; /** *         ,  *    MongoDB * * @author dinuka * */ @Document(collection = "justiceLeagueMembers") public class JusticeLeagueMemberDetail { @Id private ObjectId id; @Indexed private String name; private String superPower; private String location; public JusticeLeagueMemberDetail(String name, String superPower, String location) { this.name = name; this.superPower = superPower; this.location = location; } public String getId() { return id.toString(); } public void setId(String id) { this.id = new ObjectId(id); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSuperPower() { return superPower; } public void setSuperPower(String superPower) { this.superPower = superPower; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } } 

Spring Data es intuitivo, especialmente si tiene experiencia con JPA / Hibernate. Las anotaciones son muy similares. Lo único nuevo es la anotación.

 @Document 

que denota el nombre de la colección en la base de datos Mongo. También hay un índice definido para los nombres de superhéroes, ya que muchas consultas se relacionan con búsquedas por nombre.

Spring Data introdujo la funcionalidad de una definición de repositorio simple, lista para usar que admite operaciones CRUD regulares y algunas operaciones de lectura. Por lo tanto, en nuestra aplicación, utilizamos las capacidades de los repositorios de Spring Data, así como la clase de repositorio de la siguiente manera:

 package com.justiceleague.justiceleaguemodule.dao; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail; public interface JusticeLeagueRepository extends MongoRepository < JusticeLeagueMemberDetail, String > { /** *        ,   . *  . * * @param superHeroName *        . * @return   {@link JusticeLeagueMemberDetail}   *  . */ @Query("{ 'name' : {$regex: ?0, $options: 'i' }}") JusticeLeagueMemberDetail findBySuperHeroName(final String superHeroName); } 

Las operaciones de guardado estándar están integradas en el tiempo de ejecución de Spring a través de un proxy, por lo que solo necesita definir la clase de dominio en el repositorio.

Como puede ver, solo se define un método. Con la anotación @Query estamos buscando un superhéroe con expresiones regulares. La opción "i" significa ignorar mayúsculas y minúsculas al intentar encontrar una coincidencia en MongoDB.

Luego, procedemos a la implementación de la lógica de almacenamiento de nuevos miembros de la Liga de la Justicia a través del nivel de servicio.



 package com.justiceleague.justiceleaguemodule.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.justiceleague.justiceleaguemodule.constants.MessageConstants.ErrorMessages; import com.justiceleague.justiceleaguemodule.dao.JusticeLeagueRepository; import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail; import com.justiceleague.justiceleaguemodule.exception.JusticeLeagueManagementException; import com.justiceleague.justiceleaguemodule.service.JusticeLeagueMemberService; import com.justiceleague.justiceleaguemodule.web.dto.JusticeLeagueMemberDTO; import com.justiceleague.justiceleaguemodule.web.transformer.DTOToDomainTransformer; /** *     {@link JusticeLeagueMemberService}   * ,     * * @author dinuka * */ @Service public class JusticeLeagueMemberServiceImpl implements JusticeLeagueMemberService { @Autowired private JusticeLeagueRepository justiceLeagueRepo; /** * {@inheritDoc} */ public void addMember(JusticeLeagueMemberDTO justiceLeagueMember) { JusticeLeagueMemberDetail dbMember = justiceLeagueRepo.findBySuperHeroName(justiceLeagueMember.getName()); if (dbMember != null) { throw new JusticeLeagueManagementException(ErrorMessages.MEMBER_ALREDY_EXISTS); } JusticeLeagueMemberDetail memberToPersist = DTOToDomainTransformer.transform(justiceLeagueMember); justiceLeagueRepo.insert(memberToPersist); } } 

Una vez más, es bastante simple: si el participante ya existe, recibimos un error. De lo contrario, agregue el participante. Tenga en cuenta que esto utiliza el método Spring Data ya implementado del repositorio de inserción, que definimos anteriormente.

Finalmente, Alfred está listo para mostrar la nueva funcionalidad que desarrolló a través de la API REST usando Spring REST, para que Batman comience a enviar detalles a través de HTTP; está constantemente en movimiento:

 package com.justiceleague.justiceleaguemodule.web.rest.controller; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.justiceleague.justiceleaguemodule.constants.MessageConstants; import com.justiceleague.justiceleaguemodule.service.JusticeLeagueMemberService; import com.justiceleague.justiceleaguemodule.web.dto.JusticeLeagueMemberDTO; import com.justiceleague.justiceleaguemodule.web.dto.ResponseDTO; /** *    REST API . * * @author dinuka * */ @RestController @RequestMapping("/justiceleague") public class JusticeLeagueManagementController { @Autowired private JusticeLeagueMemberService memberService; /** *             * * @param justiceLeagueMember *    . * @return an instance of {@link ResponseDTO}     *   . */ @ResponseBody @ResponseStatus(value = HttpStatus.CREATED) @RequestMapping(method = RequestMethod.POST, path = "/addMember", produces = { MediaType.APPLICATION_JSON_VALUE }, consumes = { MediaType.APPLICATION_JSON_VALUE }) public ResponseDTO addJusticeLeagueMember(@Valid @RequestBody JusticeLeagueMemberDTO justiceLeagueMember) { ResponseDTO responseDTO = new ResponseDTO(ResponseDTO.Status.SUCCESS, MessageConstants.MEMBER_ADDED_SUCCESSFULLY); try { memberService.addMember(justiceLeagueMember); } catch (Exception e) { responseDTO.setStatus(ResponseDTO.Status.FAIL); responseDTO.setMessage(e.getMessage()); } return responseDTO; } } 

Batman no es suficiente, por lo que proporcionamos funcionalidad en forma de carga JSON, aunque Alfred sería anticuado y preferiría XML.

Nuestro viejo amigo Alfred es un secuaz de TDD (desarrollo basado en pruebas, traducido "desarrollo a través de pruebas"), por lo que quiere probar la funcionalidad. Y así, miramos las pruebas de integración escritas por Alfred para asegurarnos de que la versión inicial del sistema de administración de la Liga de la Justicia sea correcta y predecible. Tenga en cuenta que aquí solo mostramos las pruebas REST API. Alfred ha abrazado un volumen más grande, que se puede encontrar en el repositorio de GitHub .

 package com.justiceleague.justiceleaguemodule.test.util; import java.io.IOException; import java.net.UnknownHostException; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import com.fasterxml.jackson.databind.ObjectMapper; import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail; import de.flapdoodle.embed.mongo.MongodExecutable; import de.flapdoodle.embed.mongo.MongodStarter; import de.flapdoodle.embed.mongo.config.IMongodConfig; import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; import de.flapdoodle.embed.mongo.config.Net; import de.flapdoodle.embed.mongo.distribution.Version; /** *     ,     , *            . * * @author dinuka * */ @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public abstract class BaseIntegrationTest { @Autowired protected MockMvc mockMvc; protected ObjectMapper mapper; private static MongodExecutable mongodExecutable; @Autowired protected MongoTemplate mongoTemplate; @Before public void setUp() { mapper = new ObjectMapper(); } @After public void after() { mongoTemplate.dropCollection(JusticeLeagueMemberDetail.class); } /** *      mongodb     *  . * * @throws UnknownHostException * @throws IOException */ @BeforeClass public static void beforeClass() throws UnknownHostException, IOException { MongodStarter starter = MongodStarter.getDefaultInstance(); IMongodConfig mongoConfig = new MongodConfigBuilder().version(Version.Main.PRODUCTION) .net(new Net(27017, false)).build(); mongodExecutable = starter.prepare(mongoConfig); try { mongodExecutable.start(); } catch (Exception e) { closeMongoExecutable(); } } @AfterClass public static void afterClass() { closeMongoExecutable(); } private static void closeMongoExecutable() { if (mongodExecutable != null) { mongodExecutable.stop(); } } } 

 package com.justiceleague.justiceleaguemodule.web.rest.controller; import org.hamcrest.beans.SamePropertyValuesAs; import org.junit.Assert; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import com.justiceleague.justiceleaguemodule.constants.MessageConstants; import com.justiceleague.justiceleaguemodule.constants.MessageConstants.ErrorMessages; import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail; import com.justiceleague.justiceleaguemodule.test.util.BaseIntegrationTest; import com.justiceleague.justiceleaguemodule.web.dto.JusticeLeagueMemberDTO; import com.justiceleague.justiceleaguemodule.web.dto.ResponseDTO; import com.justiceleague.justiceleaguemodule.web.dto.ResponseDTO.Status; /** *      REST-,  * {@link JusticeLeagueManagementController} * * @author dinuka * */ public class JusticeLeagueManagementControllerTest extends BaseIntegrationTest { /** *          *   . * * @throws Exception */ @Test public void testAddJusticeLeagueMember() throws Exception { JusticeLeagueMemberDTO flash = new JusticeLeagueMemberDTO("Barry Allen", "super speed", "Central City"); String jsonContent = mapper.writeValueAsString(flash); String response = mockMvc .perform(MockMvcRequestBuilders.post("/justiceleague/addMember") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON).content(jsonContent)) .andExpect(MockMvcResultMatchers.status().isCreated()).andReturn().getResponse().getContentAsString(); ResponseDTO expected = new ResponseDTO(Status.SUCCESS, MessageConstants.MEMBER_ADDED_SUCCESSFULLY); ResponseDTO receivedResponse = mapper.readValue(response, ResponseDTO.class); Assert.assertThat(receivedResponse, SamePropertyValuesAs.samePropertyValuesAs(expected)); } /** *   ,       *    ,     . * * @throws Exception */ @Test public void testAddJusticeLeagueMemberWhenMemberAlreadyExists() throws Exception { JusticeLeagueMemberDetail flashDetail = new JusticeLeagueMemberDetail("Barry Allen", "super speed","Central City"); mongoTemplate.save(flashDetail); JusticeLeagueMemberDTO flash = new JusticeLeagueMemberDTO("Barry Allen", "super speed", "Central City"); String jsonContent = mapper.writeValueAsString(flash); String response = mockMvc .perform(MockMvcRequestBuilders.post("/justiceleague/addMember"). accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON).content(jsonContent)) .andExpect(MockMvcResultMatchers.status().isCreated()).andReturn().getResponse() .getContentAsString(); ResponseDTO expected = new ResponseDTO(Status.FAIL, ErrorMessages.MEMBER_ALREDY_EXISTS); ResponseDTO receivedResponse = mapper.readValue(response, ResponseDTO.class); Assert.assertThat(receivedResponse, SamePropertyValuesAs.samePropertyValuesAs(expected)); } /** *   ,      , *          JSON . *    -  . * * @throws Exception */ @Test public void testAddJusticeLeagueMemberWhenNameNotPassedIn() throws Exception { //     ,   //     . JusticeLeagueMemberDTO flash = new JusticeLeagueMemberDTO (null, "super speed", "Central City"); String jsonContent = mapper.writeValueAsString(flash); mockMvc.perform(MockMvcRequestBuilders.post("/justiceleague/addMember") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON).content(jsonContent)) .andExpect(MockMvcResultMatchers.status().is4xxClientError()); } } 

Eso es probablemente todo. Con la ayuda de Spring Boot, Alfred pudo crear rápidamente un sistema de administración de la Liga de la Justicia que funcionaba mínimamente con una API REST. Con el tiempo, ampliaremos la funcionalidad de esta aplicación y veremos cómo Alfred encuentra un enfoque para implementar la aplicación a través de Docker en una instancia de Amazon AWS administrada por Kubernetes. Nos esperan tiempos emocionantes, ¡así que conéctese!

Tradicionalmente, esperamos sus comentarios y estamos invitados a un seminario web abierto , que se llevará a cabo el 6 de febrero por el candidato de ciencias físicas y matemáticas - Yuri Dvorzhetsky .

Source: https://habr.com/ru/post/437410/


All Articles