Giter Site home page Giter Site logo

eg-peliculas-springboot-neo4j's Introduction

Ejemplo Películas con Springboot y Neo4J

Build Status

Objetivo

Testea el mapeo de una API que expone la base de grafos de Películas que viene con Neo4J.

Modelo Neo4j

El ejemplo Movies que viene con Neo4j propone

  • un nodo película (Movies)
  • un nodo para cada actor (Person)
  • y la relación entre ellos, marcada por el o los roles que cumplió cada actor en una película (ACTED_IN)

Carga inicial de datos

Instalar previamente Neo4j o bien levantar una imagen de Docker

docker run -p7474:7474 -p7687:7687 -e NEO4J_AUTH=neo4j/s3cr3t neo4j
  • Abrir el Navegador de Neo4J Desktop o bien ingresar manualmente a la URL: http://localhost:7474
  • Ejecutar el script que carga el grafo de películas (viene como ejemplo)

script inicial

Configuración

En el archivo application.yml encontrarás la configuración hacia la base de grafos, que utiliza el protocolo liviano bolt:

spring:
  data:
    neo4j:
      uri: bolt://localhost:7687
      username: neo4j
      password: #####
logging:
  level:
    org.springframework.data: DEBUG
    org.neo4j: DEBUG

Algunas consideraciones:

  • la contraseña por defecto cuando instalás localmente Neo4J es neo4j pero a veces te obliga a cambiarla, acordate de sincronizar con esta configuración (de hecho en el ejemplo de Docker estamos usando s3cr3t)
  • el puerto por defecto para el protocolo bolt es 7687
  • respecto al logging, le pusimos una configuración bastante exhaustiva: vas a ver conexiones y queries a la base. Se puede desactivar subiendo el nivel a INFO, WARN o directamente borrando la línea

Las consultas

Películas por título

Para conocer las películas en donde un valor de búsqueda esté contenido en el título (sin distinguir mayúsculas o minúsculas), y limitando la búsqueda a los primeros 10 nodos, ejecutaremos esta consulta

MATCH (pelicula:Movie) WHERE pelicula.title =~ '.*Good.*' RETURN pelicula LIMIT 10

La interfaz Neo4jRepository de Spring boot nos permite declarativamente establecer las consultas a la base, y reemplazaremos el valor concreto '.Good.' por el parámetro que recibe el contrato:

	@Query("MATCH (pelicula:Movie) WHERE pelicula.title =~ $titulo RETURN pelicula LIMIT 10")
	def List<Pelicula> peliculasPorTitulo(String titulo)

$titulo es la nueva forma de asociar el valor del parámetro titulo (hay que respetar los mismos nombres). Dado que queremos armar la expresión contiene, esto debemos hacerlo antes de llamar al repositorio, en este caso es el Service):

def buscarPorTitulo(String titulo) {
	peliculasRepository.peliculasPorTitulo(titulo.contiene)
}

contiene es en realidad un extension method definido en el archivo CipherUtils:

class CipherUtils {
	static def contiene(String valor) {
		'''(?i).*«valor».*'''.toString
	}
}

En este caso solo queremos traer el nodo película, sin sus relaciones, por lo que el endpoint devuelve una lista de personajes vacía. Esto mejora la performance de la consulta aunque hay que exponer esta decisión a quien consuma nuestra API.

Ver los datos de una película concreta

Cuando nos pasen un identificador de una película concreta, ahora sí queremos traer los datos de la película, más sus personajes y eso incluye los datos de cada uno de sus actores:

MATCH (pelicula:Movie)<-[actuo_en:ACTED_IN]-(persona:Person) WHERE ID(pelicula) = $id RETURN pelicula, collect(actuo_en), collect(persona) LIMIT 1

Es importante utilizar la instrucción collect para que agrupe correctamente los personajes y los actores.

Actualizaciones a una película

Es interesante ver que el controller delega la creación, actualización o eliminación al repositorio:

	@PostMapping("/pelicula")
	def createPelicula(@RequestBody Pelicula pelicula) {
		peliculasRepository.save(pelicula)
	}

	@DeleteMapping("/pelicula/{id}")
	def deletePelicula(@RequestBody Pelicula pelicula) {
		peliculasRepository.delete(pelicula)
	}

pero que esos métodos ni siquiera es necesario que los defina nuestra interfaz, porque ya están siendo inyectados por la interfaz Neo4jRepository (la declaratividad en su máxima expresión). El motor, en este caso Spring boot, persiste el nodo película y cualquier relación hasta el nivel de profundidad 5 que no entre en referencia circular. Anteriormente, existía un SessionManager donde podíamos tener un mayor control de la información que actualizábamos o recuperábamos: para algunos esto puede ser una desventaja, contra lo bueno que puede suponer delegar esa responsabilidad en un algoritmo optimizado.

Mapeos

Mostraremos a continuación cómo es el mapeo de las películas (las anotaciones a partir de las últimas versiones de Neo4J 4.2.x cambiaron ligeramente)

@Node("Movie")
@Accessors
class Pelicula {
	static int MINIMO_VALOR_ANIO = 1900
	
	@Id @GeneratedValue
	Long id

	@Property(name="title") // OJO, no es la property de xtend sino la de OGM
	String titulo
	
	@Property("tagline")
	String frase
	
	@Property("released")
	Integer anio
	
	@Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
	List<Personaje> personajes = new ArrayList<Personaje>

Para profundizar más recomendamos ver los otros objetos de dominio en este ejemplo y la página de mapeos de Neo4j - Spring boot

Sobre los identificadores

Por motivos didácticos hemos mantenido un ID Long que es el que genera Neo4J para sus nodos, aunque no resulta una buena estrategia, ya que cuando eliminamos nodos, Neo4j reutiliza esos identificadores para los nodos nuevos. Recomendamos investigar mecanismos alternativos para generar claves primarias, o bien tener como estrategia el borrado lógico y no físico.

Tests de integración

Elegimos hacer tests de integración sobre el repositorio, podríamos a futuro incluir al controller, pero dado que no tiene demasiada lógica por el momento estamos bien manteniendo tests más simples. Los casos de prueba que vamos a desarrollar son:

  • la búsqueda de películas, donde validaremos que se puede encontrar por "título contiene" sin distinguir mayúsculas o minúsculas y que además no trae los personajes
  • la búsqueda puntual de una película que debe traer los personajes. La forma de buscar por id requiere que luego de enviar el mensaje save guardemos el nuevo estado de la película persistida, que tiene el identificador que el container de SDN (Spring Data Neo4J) le dio.
	@Test
	@DisplayName("la búsqueda por título funciona correctamente")
	def void testPeliculasPorTitulo() {
		val peliculas = peliculasRepository.peliculasPorTitulo('''(?i).*nueve.*''')
		assertEquals(1, peliculas.size)
		assertEquals(#[], peliculas.head.personajes)
	}

	@Test
	@DisplayName("la búsqueda de una película trae los datos de la película y sus personajes")
	def void testPeliculaConcreta() {
		val pelicula = peliculasRepository.pelicula(nueveReinas.id)
		assertEquals("Nueve reinas", pelicula.titulo)
		assertEquals(2, pelicula.personajes.size)
		val darin = pelicula.personajes.findFirst [ actor.nombreCompleto.equalsIgnoreCase("Ricardo Darín")]
		assertEquals("Marcos", darin.roles.head)
	}

Para profundizar más en el tema recomendamos leer esta página

Open API / Swagger

Como de costumbre, pueden investigar los endpoints en el navegador mediante la siguiente URL:

http://localhost:8080/swagger-ui/index.html#

eg-peliculas-springboot-neo4j's People

Contributors

fdodino avatar

Watchers

Pablo Tesone avatar Gaston Prieto avatar Federico Scarpa avatar Ernesto Bossi avatar  avatar Martín Dias avatar  avatar Leo Gassman avatar Nahuel Palumbo avatar Nico Passerini avatar  avatar Marina avatar Federico Cano avatar Lucas Spigariol avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.