Skip to content

Commit b2e9a29

Browse files
committed
issues/1649 Getting started with persistence Documentation
1 parent aa4d32b commit b2e9a29

File tree

1 file changed

+142
-16
lines changed

1 file changed

+142
-16
lines changed

docs/developer-guide/getting-started-with-persistence.md

Lines changed: 142 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ We using following technology stack to deal with persistence:
1313

1414
- Embedded Cassandra as direct storage (`CassandraDaemon` allows to have the Cassandra instance inside same JVM as the application)
1515
- JanusGraph as Graph DBMS (it is not directly a data storage, it just allows you to have access to data in the form of a graph)
16-
- [Apache TinkerPop](http://tinkerpop.apache.org/docs/current/reference/) as a set of tools to interact with the database (mainly Gremlin meant here)
16+
- [Apache TinkerPop](http://tinkerpop.apache.org/docs/current/reference/) as a set of tools to interact with the database
1717
- [spring-data-neo4j](https://github.com/spring-projects/spring-data-neo4j) to manage transactions in Spring with `Neo4jTransactionManager` and implement custom Cypher queries with Spring Data repositories (by custom queries means `@org.springframework.data.neo4j.annotation.Query` annotation)
1818
- [cypher-for-gremlin](https://github.com/opencypher/cypher-for-gremlin) which translates Cypher queries into Gremlin traversals (it has some issues which prevents us to use it for `neo4j-ogm` CRUD operations, these issues will be explained below)
1919
- [neo4j-ogm](https://github.com/neo4j/neo4j-ogm) to map Java POJOs into Vertices and Edges of Graph
@@ -23,13 +23,6 @@ We using following technology stack to deal with persistence:
2323

2424
Unlike a Relational DBMS, Graph DBMS have vertices and edges, not rows and tables. So in terms of Graph every persistent entity should be stored as Vertex or Edge. An example of a vertex might be `Artifact` or `AritfactCoordinates` and the relation between them would be an edge. It should be noted that, unlike RDBMS, object relations are represented by separate edge instead of just foreign key column in table. In addition to vertices, persistence objects can also be an edges, as an example the `ArtifactDependency` would be an edge between `ArtifactCoordinates` vertices.
2525

26-
# Issues of `cypher-for-gremlin` and `neo4j-ogm`
27-
28-
First issue was the fact that `cypher-for-gremlin` not fully suport all Cypher syntax that produced by `neo4j-ogm` for CRUD operations. In more detail on every CRUD operation `neo4j-ogm` generate Cypher query which then translates into Gremlin by `cypher-for-gremlin`. As a workadound we modify Cypher queries produced by `neo4j-ogm` and replace some clauses (see `org.opencypher.gremlin.neo4j.ogm.request.GremlinRequest`).
29-
30-
Another issue is that `cypher-for-gremlin` have some doubtful concept to work with `null` values in Gremlin. They put a lot of noisy tokens into Gremlin traversals which prevents JanusGraph engine to match expected indexes, this causes heavy fullscan on every query (see [#342](https://github.com/opencypher/cypher-for-gremlin/issues/342)). This was the main reason of why we can't use `neo4j-ogm` for CRUD operations.
31-
Anyway we still using it for custom Cypher queries with `@org.springframework.data.neo4j.annotation.Query` annotation. This is good option to have Cypher queries instead of Gremlin because it looks more clear and takes less time to read existing and write new queries.
32-
3326
## Gremlin Server
3427

3528
`TODO`
@@ -56,20 +49,22 @@ the `strongbox-data-service` module.
5649
## Creating Your Entity Class
5750

5851
Let's now assume that you have a POJO and you need to save it to the database (and that you probably have at least
59-
CRUD operation's implemented in it as well). Place your code under the `org.carlspring.strongbox.domain`
52+
CRUD operations implemented in it as well). Place your code under the `org.carlspring.strongbox.domain`
6053
package. For the sake of the example, let's pick `PetEntity` as the name of your entity.
6154

6255
If you want to store that entity properly you need to adopt the following rules:
6356

64-
* Create the interface for your entity with all getters and setters that required to interact with the entity according to the `JavaBeans` coding convention. This interface should extend `org.carlspring.strongbox.data.domain.DomainObject`. The need for an interface is due to hide the implementation specific to underlying database, such as inheritance strategy.
65-
* Create the entity class which implements the above interface and have the `org.carlspring.strongbox.data.domain.DomainEntity` as the superclass.
57+
* Create the interface for your entity with all getters and setters that required to interact with the entity, according to the `JavaBeans` coding convention. This interface should extend `org.carlspring.strongbox.data.domain.DomainObject`. The need for an interface is due to hide the implementation specific details depending on underlying database, such as inheritance strategy.
58+
* Create the entity class which implements the above interface and have the `org.carlspring.strongbox.data.domain.DomainEntity` as the superclass.
59+
* Declare entity class with `@NodeEntity` or `@RelationshipEntity`
6660
* Define a default empty constructor, this would need to create entity instance from `neo4j-ogm` internals.
6761

6862
The complete source code example that follows all requirements should look something like this:
6963

7064
```java
7165
package org.carlspring.strongbox.domain;
7266

67+
@NodeEntity("Pet")
7368
public class PetEntity
7469
extends DomainEntity
7570
implements Pet
@@ -96,14 +91,145 @@ public class PetEntity
9691
}
9792
```
9893

99-
# Gremlin Repositories
94+
## Creating a `EntityTraversalAdapter`
10095

101-
As mentioned above besides `neo4j-ogm` we were forced to have custom CRUD implementation based on Gremlin. This has its advantages as it allow for us to optimize OGM entities and make them faster then common `neo4j-ogm` provide out of the box. The main thing of the Gremlin based CRUD is `EntityTraversalAdapter` which is a strategy for create/update, read/delete operations. The concrete `EntityTraversalAdapter` provide anonymous traversals for each of the operations on specific entity type. These traversals used in Gremlin based repositories to perform common CRUD operations. The `EntityTraversalAdapter` implementations can also use each other to support relations between entities, inheritance and cascade operations.
96+
As mentioned above besides `neo4j-ogm` and `spring-data-neo4j` we were forced to use custom CRUD implementations based on Gremlin. This has its advantages as it allow for us to optimize OGM entities and make them faster then common `neo4j-ogm` provide out of the box. The main thing of the Gremlin based CRUD is `EntityTraversalAdapter` which is a strategy for create/update, read/delete operations. The concrete `EntityTraversalAdapter` provide [Anonymous traversals](http://tinkerpop.apache.org/docs/current/tutorials/gremlins-anatomy/) for each of the operations on specific entity type. These traversals used in Gremlin based repositories to perform common CRUD operations:
10297

103-
## Creating a `EntityTraversalAdapter`
104-
`TODO`
98+
- `fold` to construct entity instance based on Vertex/Edge and it's properties
99+
- `unfold` to extract entity properties into Vertex/Edge and it's properties
100+
- `cascade` to cascade other Vertices/Edges within delete if needed
101+
102+
The `EntityTraversalAdapter` implementations can also use each other to support relations between entities, inheritance and cascade operations.
103+
104+
Below is the code example of `EntityTraversalAdapter` implementation for `PetEntity`:
105+
106+
```java
107+
package org.carlspring.strongbox.gremlin.adapters;
108+
109+
import static org.carlspring.strongbox.gremlin.adapters.EntityTraversalUtils.extractObject;
110+
111+
import java.util.Collections;
112+
import java.util.Map;
113+
import java.util.Set;
114+
115+
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
116+
import org.apache.tinkerpop.gremlin.structure.Element;
117+
import org.apache.tinkerpop.gremlin.structure.Vertex;
118+
import org.carlspring.strongbox.domain.Pet;
119+
import org.carlspring.strongbox.domain.PetEntity;
120+
import org.carlspring.strongbox.gremlin.dsl.EntityTraversal;
121+
import org.carlspring.strongbox.gremlin.dsl.__;
122+
import org.springframework.stereotype.Component;
123+
124+
@Component
125+
public class PetAdapter extends VertexEntityTraversalAdapter<Pet>
126+
{
127+
128+
@Override
129+
public Set<String> labels()
130+
{
131+
return Collections.singleton("Pet");
132+
}
133+
134+
@Override
135+
public EntityTraversal<Vertex, Pet> fold()
136+
{
137+
return __.<Vertex, Object>project("uuid", "age")
138+
.by(__.enrichPropertyValue("uuid"))
139+
.by(__.enrichPropertyValue("age"))
140+
.map(this::map);
141+
}
142+
143+
private Pet map(Traverser<Map<String, Object>> t)
144+
{
145+
PetEntity result = new PetEntity();
146+
result.setUuid(extractObject(String.class, t.get().get("uuid")));
147+
result.setAge(extractObject(Integer.class, t.get().get("age")));
148+
149+
return result;
150+
}
151+
152+
@Override
153+
public UnfoldEntityTraversal<Vertex, Vertex> unfold(Pet entity)
154+
{
155+
EntityTraversal<Vertex, Vertex> t = __.<Vertex>identity();
156+
if (entity.getAge() != null)
157+
{
158+
t = t.property(single, "age", entity.getAge());
159+
}
160+
161+
return new UnfoldEntityTraversal<>("Pet", t);
162+
}
163+
164+
@Override
165+
public EntityTraversal<Vertex, ? extends Element> cascade()
166+
{
167+
return __.identity();
168+
}
169+
170+
}
171+
172+
```
105173

106174
## Creating a `Repository`
107-
`TODO`
175+
176+
All the database interactions should be done through repositories. For the compatibility with `spring-data` we use `org.springframework.data.repository.CrudRepository` as a basis for our repositories. The base class for implementing `EntityTraversalAdapter`-based repositories is `org.carlspring.strongbox.gremlin.repositories.GremlinRepository`. Further repository implementation depends on the type of entity, for Vertex backed entities it should be `GremlinVertexRepository`.
177+
In addition to CRUD operations, there is also need the ability to select data using queries. Queries could be implemented using [Cypher](https://neo4j.com/docs/cypher-manual/current/introduction/) through `spring-data-neo4j` and `@org.springframework.data.neo4j.annotation.Query` annotation. So the final repository should be a class that extends `GremlinRepository` and delegates custom `Cypher` queries into `org.springframework.data.repository.Repository` instance provided by `spring-data-neo4j`.
178+
179+
Putting all above together the repository for the `PetEntity` will looks like below:
180+
181+
```
182+
package org.carlspring.strongbox.repositories;
183+
184+
import javax.inject.Inject;
185+
186+
import org.carlspring.strongbox.domain.Pet;
187+
import org.carlspring.strongbox.gremlin.adapters.PetAdapter;
188+
import org.carlspring.strongbox.gremlin.repositories.GremlinVertexRepository;
189+
import org.springframework.stereotype.Repository;
190+
191+
@Repository
192+
public class PetRepository extends GremlinVertexRepository<Pet>
193+
implements PetQueries
194+
{
195+
196+
@Inject
197+
PetAdapter adapter;
198+
199+
@Inject
200+
PetQueries queries;
201+
202+
@Override
203+
protected PetAdapter adapter()
204+
{
205+
return adapter;
206+
}
207+
208+
List<Pet> findByAgeGreater(Integer age)
209+
{
210+
return queries.findByAgeGreater(age);
211+
}
212+
213+
}
214+
215+
@Repository
216+
interface PetQueries
217+
extends org.springframework.data.repository.Repository<Pet, String>
218+
{
219+
220+
@Query("MATCH (pet:Pet) " +
221+
"WHERE pet.age > $age " +
222+
"RETURN pet")
223+
List<Pet> findByAgeGreater(@Param("age") Integer age);
224+
225+
}
226+
```
227+
228+
# Issues of `cypher-for-gremlin` and `neo4j-ogm`
229+
230+
First issue was the fact that `cypher-for-gremlin` not fully suport all Cypher syntax that produced by `neo4j-ogm` for CRUD operations. In more detail on every CRUD operation `neo4j-ogm` generate Cypher query which then translates into Gremlin by `cypher-for-gremlin`. As a workadound we modify Cypher queries produced by `neo4j-ogm` and replace some clauses (see `org.opencypher.gremlin.neo4j.ogm.request.GremlinRequest`).
231+
232+
Another issue is that `cypher-for-gremlin` have some doubtful concept to work with `null` values in Gremlin. They put a lot of noisy tokens into Gremlin traversals which prevents JanusGraph engine to match expected indexes, this causes heavy fullscan on every query (see [#342](https://github.com/opencypher/cypher-for-gremlin/issues/342)). This was the main reason of why we can't use `neo4j-ogm` for CRUD operations.
233+
Anyway we still using it for custom Cypher queries with `@org.springframework.data.neo4j.annotation.Query` annotation. This is good option to have Cypher queries instead of Gremlin because it looks more clear and takes less time to read existing and write new queries.
108234

109235
```

0 commit comments

Comments
 (0)