ReactGraph Part 3 – The Data Model

Investigation into bringing together graph and reactive computing. This is the third post in the series and deals with the platform’s data model.

Nodes and Edges

Everything in ReactGraph is a node. A node has an identity and stores a value. An edge is also a node, which means it too has identity and can store a value. Furthermore, an edge describes a directed path from a source to a destination node. The edge also has a predicate that gives some meaning to the path. An edge can be marked as “bidirectional”, which means that it also represents the destination-to-source reverse path.

The above shouldn’t be of surprise to anyone who has dealt with the representation of information using graphs. The Neo4j property graph data model is very similar (with the exception of “an edge is also a node” characteristic). Facebook’s graph data model also exhibits similar properties to that of Neo4j.

public interface INode
{
   string Id { get; }
   T Value<T>();
}

public interface IEdge : INode
{
   string SourceId { get; }
   string DestinationId { get; }
   string Predicate { get; }
   bool IsBidirectional { get; }
}

Why treat edges as nodes?

So that the can act as the source of destination of other edges. This way there is no need to reify a relationship, if the application designers don’t wish to do so. The data model is flexible. For a discussion on this see my post from 2013 entitled “On Graph Data Model Design – Relationships”.

Here’s an example I just discussed with Jim. There are multiple ways to model the data in an application of domain. One might decide to reify relationships by creating extra nodes. The best approach is the one that makes sense for the specific application.

(id: jim) -> (id: foo, predicate: watched, on: 10/10/17) -> (id: SW-IV, title: "StarWars Episode IV")
(id: savas) -> (id: bar1, predicate: liked) -> (id: foo)
(id: savas) -> (id: bar1, predicate: shared, on: 10/11/17) -> (id: foo)

Graph Queries and Reactive Computing

With the graph data representation in place, we move to the basic abstractions necessary to support both “pull” graph querying and “push” continuous querying (reactive computing). The interface for a node is expanded with four additional properties.

public interface INode
{
   string Id { get; }
   T Value<T>();

   IQueryable<IEdge> IncomingEdges { get; }
   IQueryable<IEdge> OutgoingEdges { get; }

   IQbservable<IValueEvent<INode>> ValueChanges { get; }
   IQbservable<IEdgeEvent> EdgeChanges { get; }
}

The IncomingEdges and OutgoingEdges let us create query expressions that can be submitted for evaluation to the data provider supporting the respective IQueryable interfaces. The properties represent the collection of incoming and outgoing edges respectively.

The ValueChanges property allows the construction of Rx expression trees that can be submitted for continuous evaluation at the reactive computing data provider supporting the implementation of the INode interface. The ValueChanges represents the stream of changes to the value that the INode holds.

Similarly, the EdgeChanges allows the construction of Rx expression trees that can be submitted for continuous evaluation at the reactive computing data provider supporting the implementation of the INode interface. The EdgeChanges represents the stream of changes to the incoming and outgoing edges (e.g. when a new edge is created or deleted with the specific INode as a source of destination).

Please note that it’s an open design issue whether these properties should really be part of the INode interface. They can easily be provided as extension methods on INode by an implementor.

Graph operations

One could interact with the ReactGraph active computing store through an implementation of the following interface.

public interface IGraph
{
   IQueryable<INode> Nodes { get; }
   IQueryable<IEdge> Edges { get; }

   IQbservable<IValueEvent<INode>> ValueChanges { get; }
   IQbservable<IEdgeEvent> EdgeChanges { get; }

   Task<INode> GetNode(string id);
   Task<IEdge> GetEdge(string id);

   Task<INode> NewNode<T>(string id, T value = default(T));
   Task<IEdge> NewEdge<T>(
      string id,
      string sourceId,
      string predicate,
      string destinationId,
      T value = default(T),
      bool bidirectional = false);

   Task Update<T>(string id, T value);

   Task Delete(string id);
}

The interface should be self-explanatory.

The final INode interface

During the implementation stage of the interfaces, it became aparent that INode could use a couple of more things. Again, it’s an open design issue whether these should be part of the core interface.

public interface INode
{
   string Id { get; }

   T Value<T>();
   Type ValueType { get; }

   IGraph Graph { get; }

   IQueryable<IEdge> IncomingEdges { get; }
   IQueryable<IEdge> OutgoingEdges { get; }

   IQbservable<IValueEvent<INode>> ValueChanges { get; }
   IQbservable<IEdgeEvent> EdgeChanges { get; }
}

Miscellaneous

Finally, for completeness, here are the event-related interfaces. It’s an open design issue whether these two interfaces could be consolidated somehow.

public interface IValueEvent<out T>
{
   ChangeOperation Operation { get; }
   T Item { get; }
}
public interface IEdgeEvent
{
   INode Node { get; }
   IEdge Edge { get; }
   ChangeOperation Operation { get; }
}
public enum ChangeOperation
{
   New,
   Delete,
   Update,
}

With the basic abstractions in place, we are now ready to start exploring what we can do with these. Then we will move to the implementation of the “(re)active graph store”.

I will try to make the code available on github as soon as possible.

Next: ReactGraph Part 4 – First (Re)Active Queries

Savas Parastatidis

Savas Parastatidis works at Amazon as a Sr. Principal Engineer in Alexa AI'. Previously, he worked at Microsoft where he co-founded Cortana and led the effort as the team's architect. While at Microsoft, Savas also worked on distributed data storage and high-performance data processing technologies. He was involved in various e-Science projects while at Microsoft Research where he also investigated technologies related to knowledge representation & reasoning. Savas also worked on language understanding technologies at Facebook. Prior to joining Microsoft, Savas was a Principal Research Associate at Newcastle University where he undertook research in the areas of distributed, service-oriented computing and e-Science. He was also the Chief Software Architect at the North-East Regional e-Science Centre where he oversaw the architecture and the application of Web Services technologies for a number of large research projects. Savas worked as a Senior Software Engineer for Hewlett Packard where he co-lead the R&D effort for the industry's Web Service transactions service and protocol. You can find out more about Savas at https://savas.me/about

Recent Posts

BrainExpanded – The Timeline

See "BrainExpanded - Introduction" for context on this post. Notes and links Over the years,…

2 days ago

BrainExpanded – Introduction

This is the first post, in what I think is going to be a series,…

2 days ago

Digital twin follow up

Back in February, I shared the results of some initial experimentation with a digital twin.…

2 weeks ago

Digital Twin (my playground)

I am embarking on a side project that involves memory and multimodal understanding for an…

10 months ago

“This is exactly what LLMs are made for”

I was in Toronto, Canada. I'm on the flight back home now. The trip was…

1 year ago

AI is enhancing me

AI as an enhancer of human abilities.

1 year ago