ReactGraph Part 4 – First (Re)Active Queries

Investigation into bringing together graph and reactive computing. This fourth post in the series showcases LINQ-to-graph and Rx types queries for both “pull” and “push” type computation.

Disclaimer

Remember that the goal of the investigation is to think about the abstractions. We don’t deal with implementation issues such as scale, reliability, hot spots, partitioning, locality of access, performance, etc. Those are very very important issues and the most difficult part of any implementation. If you are looking to work with graphs, you should really look at Neo4j.

Adding data

I picked a domain familiar to everyone… the social graph. First, let’s add some people…

var savas = await graph.NewNode("savas", new Person { FirstName = "Savas", LastName = "Parastatidis" });
var jim = await graph.NewNode("jim", new Person { FirstName = "Jim", LastName = "Webber" });
var erik = await graph.NewNode("erik", new Person { FirstName = "Erik", LastName = "Meijer" });
var adam = await graph.NewNode("adam", new Person { FirstName = "Adam", LastName = "Wolff" });

Now let’s create some relationships between these nodes…

await graph.NewEdge(
   "savas-friend-jim",
   savas.Id,
   "friend",
   jim.Id,
   new Friend { Since = new DateTime(1995, 10, 1) },
   true);

// ... More person-friend-person edges

await graph.NewEdge(
   "savas-colleague-erik-1",
   savas.Id,
   "colleague",
   erik.Id,
   new Coleague
   {
      From = new DateTime(2010, 10, 1),
      Until = new DateTime(2011, 10, 1),
      Company = "Microsoft"
   },
   true);

// ... More person-colleague-person edges

Let’s add some posts…

var post = await graph.NewNode(
   "savas-status-update-1",
   new StatusUpdate
   {
      Text = "Status update 1",
      CreatedOn = DateTime.Now
   });
await graph.NewEdge(
   "savas-post-status-update-1",
   user.Id,
   "post",
   post.Id,
   new Post { PostedOn = DateTime.Now });
// ... More posts 

In a similar way, we can add comments, representations of photos, likes, pokes, etc.

First query

First, a note about the difference between nodes/edges and the values they store. You will notice that when we write queries against the ReactGraph store, we are dealing with nodes and edges, not values. We will see operators that help us write queries against those values, taking advantage of LINQ’s deep integration with the language (e.g. inline completion).

// Get the INode in the graph with identity "savas"
var savasNode = await graph.Get("savas");

// Project the value stored at the INode to an instance of the 
// type Person. We only need this when we want to deal with the 
// value inside a node or an edge.
var savasValue = savasNode.Value<Person>();

Now, let’s get one’s friends. A common theme across all the examples is the heavy use of LINQ and existing query operators wherever possible. This was actually a key goal of this investigation. More specialized/advanced query operators could (and will) be writen.

var friends = savas
  .OutgoingEdges
  .Where(e => e.Predicate == "friend")
  .Select(e => e.Graph.Get(e.DestinationId).Result));
  
foreach (var f in friends) {
  WriteLine($"Savas friends with {f.Value<Person>().FirstName});
}

// --- Output:
// Jim
// Eric
// Adam

Cool eh?

Note that the e.Graph.Get(e.DestinationId).Result part is really ugly. Unfortunately, the LINQ operators don’t compose great when async delegates are used. This is an open design issue that I will try to address.

The above pattern is common enough that I have provided helper operators (extension methods)…

var friends = savas
  .Outgoing("friend")
  .DestinationNode();

In both cases, friends is of type IQueryable<INode>. If we wanted to project to the Person type, we could have written the query above as…

var friends = savas
  .Outgoing("friend")
  .DestinationNode()
  .ValueType<Person>();

ValueType() is another helper extension method on IQueryable<INode>.

I hope you are with me so far and having fun 🙂

Now let’s get a user’s posts and start getting reactive

We can use a very similar query to the one above to get the user’s posts…

var posts = savas
  .Outgoing("post")
  .DestinationNode()
  .ValueType<StatusUpdate>();
  
posts.ToList().ForEach(p => WriteLine(p.Text));
// --- Output
// Status update 1
// Status update 2
// ...

In fact, we could even create domain-specific operators to make writing such queries even easier. Tooling that automatically generates code for us can take care of the mandane task of writing such helper operators.

public static class SocialGraphOperators {
  public static IQueryable<Person> Posts(this IQueryable<INode> nodes) {
    return nodes
      .Outgoing("post")
      .DestinationNode()
      .ValueType<StatusUpdate>();
  }
}

// Now we can get the posts like this...
var posts = savas.Posts();
  
posts.ToList().ForEach(p => WriteLine(p.Text));
// --- Output
// Status update 1
// Status update 2
// ...

Hopefully no surprise so far. But if we want to get the most recent list of a user’s posts, we need to keep submitting the same query. Well, that’s not great. How could we express our interest to receive any new posts that are made? Let’s enter the world of our reactive graph.

As before, here’s how the query expression would look like without any helper operators…

var posts = savas
  .EdgeChanges
  .Where(e => e.Node.Id == e.Edge.SourceId && e.Edge.Predicate == "post")
  .Select(e => e.Graph.GetNode(e.Edge.DestinationId));

// Using helper operators provided by the ReactGraph LINQ library
var posts = savas
  .EdgeChanges
  .Outgoing("post")
  .DestinationNode();
  
// Now subscribe
posts.Subscribe(p => WriteLine($"New post: {p.Text}");

// Of course, there is no output... We need to wait for 
// something to happen. Let's add some posts
await AddNewPost("savas", "foo");
await AddNewPost("savas", "bar");

// We need to enter an event loop
ReadLine();

// --- Output
// New post: foo
// New post: bar

And now, here’s the same Rx query looks like with the helper operators provided with the ReactGraph library…

var posts = savas
  .EdgeChanges
  .Outgoing("post")
  .DestinationNode();  

In both cases, the type of posts is IQbservable<INode> (note the “Q”). This means that the expression tree can be remoted and hosted at a remote store (which isn’t the case yet with the current implementation of course).

Please note how similar the pull and push queries look. That’s the beauty of LINQ.

As before, we could add domain-specific operators to make the query even easier to read.

In subsequent posts, we are going to dive into even more complex queries, both pull and push.

Next: A user’s feed via “pull” and “push” graph 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.…

1 week 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