diff --git a/docs/object_mapping.md b/docs/object_mapping.md new file mode 100644 index 0000000000000000000000000000000000000000..cc867f42a3b5489e2d3b2b8a3c5046785560d029 --- /dev/null +++ b/docs/object_mapping.md @@ -0,0 +1,213 @@ +Ferma is an Object-graph Model (OGM). An Object-graph Model is to a Graph Database as an Object-relational Model (ORM) +is to a Relational Database. That is to say that it maps Java Objects to edges and vertex in a graph database. As a +natural consequence the Java types become an implied Schema for a Graph Database even if the underlying implementation +doesnt support the notion of a schema. + +The objects associated with the various types of Edges and Vertex in a graph are collectively called the Graph Data +Model (GDM). Each Java type in the GDM will usually represent a class of Edges or Vertex in underlying graph. All Edges +in the model will extend from the `EdgeFrame` interface and all vertex will extend from `VertexFrame` interface. The +individual classes that comprise the GDM are usually simply refered to as Frames. + +The methods defined by a Frame will represent interactions with the underlying graph via traversals that are relative, +using the current edge or vertex as their starting point. + +```java +public interface Person extends VertexFrame { + String getName(); + List<? extends Person> getCoworkers(); +} +``` + +In this example Person represents a vertex in the graph with a property indicating their name, and they are associated +with other vertex in the graph of the same type that represent their coworkers. + +When implementing a vertex as a concrete class you must instead inherit from `VertexFrame`. + +```java +public class Person extends AbstractVertexFrame { + public String getName() { + return this.getProperty("name"); + } + + public List<? extends Person> getCoworkers() { + return this.traverse(v -> v.out("coworker")).toList(Person.class); + } +} +``` + +It is also possible to do the same with inheritance if you want a class and an interface defined. + +```java +public class PersonImpl extends AbstractVertexFrame implements Person { + @Override + public String getName() { + return this.getProperty("name"); + } + + @Override + public List<? extends Person> getCoworkers() { + return this.traverse(v -> v.out("coworker")).toList(Person.class); + } +} +``` + +!!! Note + When implementing a Frame a class or abstract class must always extend from either `AbstractEdgeFrame` or + `AbstractVertexFrame`. + +## Typing + +There are two typing modes for ferma and each significantly effect how the user will determine the type of the objects +pulled from the graph, these modes are called **Typed Mode** and **Untyped Mode**. + +When performing a traversal on a Frame there are several methods provided which automatically encapsulate the underlying +graph element or elements into a framed equivelant such as a `VertexFrame` or a `EdgeFrame`. This may either be a single +Frame, Iterator, Set, or List of Frames. + +In the earlier example we used a traversal to find all the coworkers, we used the `toList()` method to frame all the +underlying vertex into the `Person` type. + +```Java +this.traverse(v -> v.out("coworker")).toList(Person.class); +``` + +Traversals have several different methods availible that each frame and collect the underlying elements in different +ways, those methods, members of the `Traversable` interface, are the following. + +```Java +<N> N next(Class<N> kind); +<N> List<? extends N> next(int amount, Class<N> kind); +<N> N nextOrDefault(Class<N> kind, N defaultValue); +VertexFrame nextOrAdd(); +<N> N nextOrAdd(ClassInitializer<N> initializer); +<N> N nextOrAdd(Class<N> kind); +<N> Iterator<N> frame(Class<N> kind); +<N> List<? extends N> toList(Class<N> kind); +<N> Set<? extends N> toSet(Class<N> kind); +``` + +!! note + Each of these methods also have an equivelant method with the suffix `Explicit`, we will discuss those later as they + only become important when we begin to discuss the differences between typed and untyped mode. + +Each of these methods has a slightly different behavior. For full details see the Ferma Javadocs for the Traversable +class. However, in short, the `next(Class)` method returns any one of the matching elements and frames it as the +specified type. It will throw an exception however if no vertexes match. The `nextOrDefault` varient avoids the +exception by returning the default value when there are no matches, which can be `0` or `null` for example. Similarly +`nextOrAdd` will add a new vertex to the underlying graph if the traversal yields no matches. finally `frame(Class)`, +`toList(Class)`, and `toSet(Class)` will return all elements that match the traversal as either a `Iterator`, `List`, +or a `Set`. + +The exact type returned from all the aforementioned calls will always be a Class of the type specified in the argument, +or a subclass thereof. The exact type of the class instantiated will depend on which typing mode is being used. + + +### Untyped Mode + +In untyped mode there is never any Java type information encoded into the underlying graph. This means when you take an +object off the graph there is no way for Ferma to know what Java type it is associated with and the user must select +the type manually. Since a Frame just defines a set of behaviors and properties exposed for a particular graph +element it can sometimes be useful to pick which Frame to use to represent an element based on how you need to interact +with that element rather than a one to one mapping of element to a specific type. In such a scenario Untyped Mode might +be the ideal choice. + +In this mode when framing elements from a traversal the type of the element is determined entierly from the parameters +passed to the methods invoked on the Traversable class. The following is an example of how to frame a vertex as a +`Person` class from above. + +```Java +// Open an untyped Framed Graph +FramedGraph fg = new DelegatingFramedGraph(TinkerGraph.open()); + +//create a vertex with no type information and a single name property +VertexFrame vertex = fg.addFramedVertex(VertexFrame.class); +vertex.setProperty("name", "Jeff"); + +//retrieve the vertex we just created but this time frame it as a Person +Person person = fg.traverse(g -> g.V().property("name", "jeff")).nextExplicit(Person.class); +assert person.getName().equals("Jeff"); +``` + +!!! note + In untyped mode all the `Traversal` methods with the suffix of `Explicit` behave exactly the same as those methods + without the suffix. Therefore when working in untyped mode it is suggested you only use explicit methods. This way + if you ever decide to migrate over to typed mode it will not change the behavior of your existing code base and will + make the migration process much easier. + +## Typed Mode + +Typed mode takes things one step further and allows type information about a Data Model class to be encoded as a +property on vertex and edges in the underlying graph. This behavior is governed by the `PolymorphicTypeResolver` which +encodes the type in a property name which defaults to the value of `PolymorphicTypeResolver.TYPE_RESOLUTION_KEY` but can +be explicitly set to any string value of the user's choice. When a class is framed the Type Resolution Key is read and +the original type is determined, this in turn effects the type used to instantiate the new Frame and may be a specific +type which is a subclass of the type requested. For example say we have the following model. + +```Java +public class Person extends AbstractVertexFrame { + public String getName() { + return this.getProperty("name"); + } + + public List<? extends Person> getFriends() { + return this.traverse(v -> v.out("friend")).toList(Person.class); + } +} + +public class Programmer extends Person { + @Override + public List<? extends Programmer> getFriends() { + //Programmers don't have friends :( + return Collections.emptyList(); + } +} +``` + +In this case we can encode a `Programmer` vertex into the graph and even if we try to retrieve and frame that vertex as a +`VertexFrame` or `Person` in the future the instantiated type will still be `Programmer`. This allows for a truly +Polymorphic Graph Data Model that leverages method overriding and class inheritance functiuonality in the model. For +example the following is possible now in Typed Mode. + +```Java +// Open typed Framed Graph +FramedGraph fg = new DelegatingFramedGraph(TinkerGraph.open(), true, false); + +//create a vertex with no type information and a single name property +Programmer programmer = fg.addFramedVertex(Programmer.class); +programmer.setName("Jeff"); + +//retrieve the vertex we just created and check it is instantiated as a Programer +Person person = fg.traverse(g -> g.V().property("name", "jeff")).next(Person.class); +assert person instanceof Programmer; +assert person.getFriends().isEmpty(); +``` + +The methods with the `Explicit` suffix are particularly meaningful for Typed Mode. In this mode they bypass the encoded +typing completely and instantiate the frame as the type specified instead. The following code snippet provides an +example using the same model. + +```Java +// Open typed Framed Graph +FramedGraph fg = new DelegatingFramedGraph(TinkerGraph.open(), true, false); + +//create a vertex with no type information and a single name property +Programmer programmer = fg.addFramedVertex(Programmer.class); +programmer.setName("Jeff"); + +//retrieve the vertex we just created, since we are using an excplicit method the type won't be Programmer this time. +Person person = fg.traverse(g -> g.V().property("name", "jeff")).nextExplicit(Person.class); +assert !(person instanceof Programmer); +``` + +The following are the list of explicit method types in the Traversable class. + +```Java +<N> N nextExplicit(Class<N> kind); +<N> List<? extends N> nextExplicit(int amount, Class<N> kind); +<N> N nextOrDefaultExplicit(Class<N> kind, N defaultValue); +<N> N nextOrAddExplicit(ClassInitializer<N> initializer); +<N> N nextOrAddExplicit(Class<N> kind); +<N> Iterator<? extends N> frameExplicit(Class<N> kind); +<N> List<? extends N> toListExplicit(Class<N> kind); +<N> Set<? extends N> toSetExplicit(Class<N> kind); +``` diff --git a/mkdocs.yml b/mkdocs.yml index c388349daf2d39f22015ed5a6ea0046087865bf2..861e69fb5a26190a7a5f1f82193e98b2dfc2f966 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,7 +11,7 @@ site_url: 'http://syncleus.com/Ferma' pages: - Home: index.md - Getting Started: getting_started.md - - Comparing the Alternatives: comparing_the_alternatives.md + - Object Mapping: object_mapping.md - Core Annotations: - Overview: annotations/overview.md - '@Adjacency': annotations/adjacency.md @@ -20,6 +20,7 @@ pages: - '@InVertex': annotations/invertex.md - '@OutVertex': annotations/outvertex.md - '@Property': annotations/property.md + - Comparing the Alternatives: comparing_the_alternatives.md - Glossary: glossary.md extra: version: '3.2.1'