Haciendo que los grafos sean divertidos de nuevo con Java

Por Otavio Santana Java Champion Oracle Developer Champion
Publicado en Octubre 2018

Revisado por Alexis Lopez




La tecnología NoSQL se ha popularizado en las más diversas áreas y con diferentes usos, lo que incluye casos de éxito con bases de datos de grafos. Las bases de datos de grafos tienen una estructura bastante diferente a la de las bases de datos relacionales y su uso ha sido bastante grande, sobre todo en sistemas de recomendación. El objetivo de este artículo es cubrir lo que esta tecnología hace, su estructura y ventajas más allá de su uso con Java. 





La estructura de un grafo




Las bases de datos de grafos se caracterizan principalmente por la posibilidad de almacenar las relaciones de manera bastante profunda, estas relaciones poseen dirección y propiedades, muy común en recomendación, después de todo, el hecho de que alguien conozca a un artista famoso no es recíproco, o sea, que ese artista también conozca a esa persona.

Básicamente, los elementos de los grafos son:

Propiedad: Esta es la unidad más pequeña de la estructura, está compuesta por una tupla en la que la clave es el nombre de la propiedad y el valor es la información. Es similar a Map.Entry de un java.util.Map.

Vertex: Al intentar realizar una comparación con el mundo relacional esto sería similar a las tablas que poseen una cantidad ilimitada de propiedades. Es importante señalar que la mayoría de las bases de datos del tipo grafo no poseen una estructura definida (se denominan schemaless), es decir, ellas "nacen" en el momento de la ejecución (a diferencia de una tecnología de persistencia relacional que tiene esquemas, o sea, el campo estará allí, pero, si la información no existe se definirá como nula.

Edge: este objeto representa la relación entre los vértices que, al igual que el Vertex, también posee un número indefinido de propiedades además de la dirección de la relación. Es decir, la relación posee propiedades y sentido lo que hace que el grafo tenga una mayor profundidad que las bases de datos relacionales. El sentido de la relación en el grafo es un concepto realmente importante, como ya se ha mencionado, una persona puede conocer a un artista a pesar de que ese artista no le conozca. 




Algunas bases de datos de tipo grafo

Hay varias bases de datos de tipo grafo, siendo la más famosa  Neo4J.

• Neo4j

• ArangoDB

• Titan

• OrientDB


El inmenso número de APIs

En conjunto con el gran número de base de datos de tipo grafo, hay un gran número de APIs. Generalmente, cada base de datos posee su propia sintaxis para la comunicación. Este enfoque trae diversos problemas, como por ejemplo, un sistema grande quedará "rehén" de aquel proveedor de base de datos, ya que existe un alto costo en el cambio, además de que la curva de aprendizaje acaba siendo elevada. 





TinkerPop


Teniendo en cuenta el bloqueo entre bases de datos del tipo grafo, ya explicado anteriormente, nació el Apache Tinkerpop que es una solución de código abierto provista por la Fundación Apache. Su objetivo es traer una API estándar para las bases de datos del tipo grafo, piense en JDBC para grafos, que incluye soporte tanto para operaciones de inserción, como de actualización y eliminación, como para consulta con el Gremlin. En el momento en que escribo este artículo, Apache Tinkerpop tiene soporte para más de 30 bases de datos. 





Grafos sobre la tecnología relacional


Observe la figura siguiente; la pregunta más simple sería "cómo representar una cadena alimentaria en una estructura alimentaria?". La posibilidad, sin embargo, de crear tal estructura no será tan simple como si se representara con grafos, después de todo, el sentido de quién es el predador y quién es la presa importa. 


Pensando en grafos la consulta se vuelve bastante simple. En el mundo relacional existe el SQL que es un estándar para la consulta y los cambios de datos del mundo relacional. En el mundo de los grafos existe algo parecido que es el Gremlin, en el que es posible realizar consultas y cambios en los elementos del Grafo (Vertex y Edge). Pensando en el ejemplo citado anteriormente, la cadena alimentaria, es posible recorrer la información con Gremin utilizando una estructura similar a la del while y el for. 

<pre>

g.V().repeat(out("eats")).times(3);//to go three times in the graph

g.V().repeat(out("eats")) .until(has("name", "grass"));//to go as a "do while" structure

</pre>





Simple, ¿no? Pero, ¿y mis entidades? 

                    

El Apache TinkerPop es compatible con Java, pero sólo como API de comunicación y la estructura de grafos no es similar a las entidades que encontramos en el mundo corporativo, por ejemplo, entidad Persona o Libro en el que los desarrolladores Java están acostumbrados a trabajar. 





Grafos y Eclipse JNoSQL


En el mundo de grafos, Eclipse JNoSQL tiene una integración con el Apache Tinkerpop como capa de mapeo. Así, comparado con el mundo relacional, el Apache Tinkerpop sería como el JDBC y el Eclipse JNosQL como JPA. Las siguientes secciones ilustrarán la  integración entre estas dos herramientas. 


Ejemplo de Mapeamiento

Un punto a destacar en el mapeo es su gran semejanza con el JPA. El objetivo es bastante simple: utilizar la nomenclatura que el desarrollador Java ya está acostumbrado a utilizar teniendo en cuenta que la tecnología utilizada es NoSQL. Por ejemplo, como el concepto de entidad no es específico de las bases de datos relacionales, es posible utilizarlo sin problemas. Para la conversión entre un vértice y una instancia, los métodos de acceso (getter y setter) no son obligatorios y como en CDI, es necesario que la entidad posea un constructor no privado y sin argumentos.

<pre>
@Entity
public class Book {
   @Id
   private Long id;
   @Column
  private String name;
}
@Entity
public class Person {
   @Id
   private Long id;
   @Column
   private String name;
   @Column
   private int age;
   @Column
   private String occupation;
   @Column
   private Double salary;
}
</pre>





Utilizando JNoSQL


El primer paso para la integración de Eclipse JNoSQL es hacer que el grafo de TinkerPop sea visible para el CDI. Para hacer esto es necesario que exista un método que produzca el grafo. Por ejemplo, para utilizar el Neo4J embarcado como se muestra en el siguiente código:

<pre>
@ApplicationScoped
public class GraphProducer {
  private Graph graph;
  @PostConstruct
  public void init() {
    String absolutePath = "any path";
    this.graph = Neo4jGraph.open(absolutePath);
  }
  @Produces
  @ApplicationScoped
  public Graph getGraph() {
    return graph;
  }
}
</pre>



Tan pronto como una instancia de Graph sea visible por el CDI, el siguiente paso es inyectar un GraphTemplate. El GraphTemplate es similar al estándar Template Method, pero enfocado en operaciones con Grafos y con él es posible crear vertices, que serán las Entidades definidas, además de las relaciones entre las entidades, que son los Edge, los cuales serán respresentados en Eclipse JNoSQL como EdgeEntity.

<pre>
@Inject
private GraphTemplate graph;
//to CDI 2.0 with Java SE
GraphTemplate graph = container.select(GraphTemplate.class).get();
</pre>



A partir de ese GraphTemplate es posible insertar las entidades en la base de datos además de realizar las conexiones de las mismas, y es responsable también de realizar las búsquedas con el envoltorio del Gremlin que será explicado a continuación.

<pre>
Person poliana = Person.builder().withName("Poliana").withAge(25).build();
Book shack = Book.builder().withAge(2007).withName("The Shack").build();
graphTemplate.insert(poliana);
graphTemplate.insert(shack);
//relationship
EdgeEntity reads = graphTemplate.edge(poliana, "reads", shack);
reads.add("where", "Brazil");
</pre>





Realizando consultas JNoSQL y TinkerPop 


Para ilustrar la funcionalidad de los grafos con el Tinkerpop, definiremos el siguiente escenario:

Imagine una campaña de marketing, esa campaña tiene el objetivo de alcanzar el siguiente público:

• Un desarrollador
• Salario mayor e igual a $ 3,000
• Tener edad entre 20 y 25 años

<pre>
//In this scenario both SQL and Tinkerpop easy to do.
List<Person> developers = graph.getTraversalVertex()
    .has("salary", gte(3_000D))
    .has("age", between(20, 25))
    .has("occupation", "developer")
    .<Person>stream().collect(toList());
</pre>



Este primer paso es muy sencillo hacerlo tanto en grafos como en el mundo relacional, sin embargo, habrá un segundo paso en este ejemplo que hará la consulta más difícil. A partir del hecho de que una persona que no esté en ese perfil pueda conocer a una persona que sí lo esté y así poder realizar la recomendación o simplemente que compre el producto, el próximo paso tendrá el objetivo de traer a las personas que nuestro objetivo conoce:

<pre>
List<Person> result = graph.getTraversalVertex()
.has("salary", gte(3_000D))
.has("age", between(20, 25))
.has("occupation", "developer")
.out("knows");
</pre>



¡La campaña fue un éxito! Pero el día de los enamorados está llegando y con él una nueva campaña de marketing. Para este caso es necesario que, además del desarrollador, también obtengamos a su enamorada. En este caso, el principio es que el público objetivo sea conocido por una persona  y además ese conocido sienta amor por el desarrollador. Como se ha visto, las relaciones poseen propiedades, así que será necesario verificar la propiedad de ese Edge (inE) y luego verificar sus propiedades como muestra el código siguiente: 

<pre>
List<Person> love = graph.getTraversalVertex()
.has("salary", gte(3_000D))
.has("age", between(20, 25))
.has("occupation", "Developer")
.inE("knows")
.has("feel", "love")
.bothV()
.<Person>stream()
.distinct()
.collect(toList());
</pre>



Con ello se presentó un nuevo concepto de persistencia con Grafos, además de sus posibilidades de uso como jerarquía, cadena alimentaria o una estructura de una empresa, o relaciones mucho más densas que exijan que las relaciones tengan propiedades. Si una aplicación tiene un modelo complejo a punto de requerir varias relaciones N para N en una base relacional, tal vez sea un buen indicio para mirar cuidadosamente hacia otro paradigma de persistencia como es el caso de las bases de datos de tipo grafos, que así como el relacional, también cuenta con un estándar de comunicación única, en este caso con Apache Tinkerpop.





Referencias





Otavio Santana es un ingeniero de software centrado en la tecnología Java. Tiene experiencia principalmente en aplicaciones de alto rendimiento en finanzas, gobiernos, redes sociales y comercio electrónico. El conocimiento y la experiencia de Otavio se encuentran en Java SE, Java EE, Spring y Google App Engine (GAE). También trabaja con varias bases de datos tanto SQL como NoSQL. Orador, vicepresidente de SouJava, Otavio es miembro de varios grupos de expertos de JSR, miembro ejecutivo de JCP y colaborador de JBoss Weld, Hibernate, Apache Commons. Ha recibido un Premio a la Excelencia JCP, un Premio Dukes Choice y un Premio Java Champion. twitter: @otaviojava

Este artículo ha sido revisado por el equipo de productos Oracle y se encuentra en cumplimiento de las normas y prácticas para el uso de los productos Oracle.