Social graph
Social graphs are a common feature of many modern mobile, gaming, and web applications. This page will walk you through the process of creating a simple social graph. The example we’ll build here can support features such as timelines, suggested followers, and Erdős numbers.
This tutorial assumes that you have completed the Dashboard quick start. |
You can also complete this tutorial with the Fauna Shell. All the commands work the same way in the Dashboard Shell and the Fauna CLI. |
Step 1: Open the Fauna v4 Dashboard
Log in to the v10 Fauna Dashboard and access the Fauna v4 Dashboard by clicking clicking the v4 Dashboard tab for an exiting database.
In the v4 Dashboard, click the database my_db
in your list of databases.
Step 3: Create a collection and index to represent people
The first collection that we need to create is people
, to represent
the users in our social graph. Users could be players in a game,
subscribers to an author’s articles, or coworkers in a professional
network.
CreateCollection({ name: "people" })
Then, we need to create an index on peoples' names. The index allows us
to refer to people with names like "alice" rather than refs like
Ref(Collection("people"), 1)
. Also, let’s use the unique constraint to
ensure that multiple users don’t have the same name.
CreateIndex({
name: "people_by_name",
source: Collection("people"),
terms: [{ field: ["data", "name"] }],
unique: true
})
Step 4: Create a collection and index to represent relationships
The connection between people is called a "relationship". Relationships are directional: a person may follow another person without requiring the second person to reciprocate the relationship. Let’s call the first person a "follower" of the "followee".
CreateCollection({ name: "relationships" })
Then, we need to create an index on the relationships
collection which
is the core of our social graph. This index allows us to easily answer
questions such as "who follows person A?" or "who follows person A, but
not person B?"
CreateIndex({
name: "followers_by_followee",
source: Collection("relationships"),
terms: [{ field: ["data", "followee"] }],
values: [{ field: ["data", "follower"] }]
})
Step 5: Populate the graph
Now that we have collections representing the people in our graph
(people
) and their relationships to each other (relationships
), we
can begin populating the graph.
First, let’s create four users: Alice, Bob, Carol, and Dave. Notice that
we added an index on the field ["data", "name"]
for the people
collection. Later on, we can use that index to find the four people in
our graph.
Foreach(
["Alice", "Bob", "Carol", "Dave"],
Lambda("name",
Create(Collection("people"), { data: { name: Var("name") } })
)
)
In the following queries, we use this query pattern:
Get(Match(Index("people_by_name"), "Alice"))
This query uses the index people_by_name
to find the first person with
the name "Alice". We can be sure that the first person returned is the
one we’re looking for because of the uniqueness constraint we set when
we created the index.
The first relationship that we are going to create is between Alice and Bob. We’ll create a relationship with Alice as the follower, and Bob as the followee — in plain English, this says "Alice follows Bob."
Create(
Collection("relationships"),
{
data: {
follower: Select("ref", Get(Match(Index("people_by_name"), "Alice"))),
followee: Select("ref", Get(Match(Index("people_by_name"), "Bob")))
}
}
)
Next, let’s add a relationship in the other direction: Bob follows Alice.
Create(
Collection("relationships"),
{
data: {
follower: Select("ref", Get(Match(Index("people_by_name"), "Bob"))),
followee: Select("ref", Get(Match(Index("people_by_name"), "Alice")))
}
}
)
Let’s use the index people_by_name
to find all users named either
"Alice" or "Bob" and add a relationship to Carol — i.e. Carol follows
both Alice and Bob.
Let(
{
follower: Select("ref", Get(Match(Index("people_by_name"), "Carol")))
},
Foreach(
Paginate(
Union(
Match(Index("people_by_name"), "Alice"),
Match(Index("people_by_name"), "Bob")
)
),
Lambda("followee",
Create(
Collection("relationships"),
{
data: {
followee: Var("followee"),
follower: Var("follower")
}
}
)
)
)
)
Finally, let’s add a relationship meaning, "Dave follows Alice."
Create(
Collection("relationships"),
{
data: {
followee: Select("ref", Get(Match(Index("people_by_name"), "Alice"))),
follower: Select("ref", Get(Match(Index("people_by_name"), "Dave")))
}
}
)
Step 6: Explore the graph
We now have four users and relationships among them in our social graph:
-
Alice follows Bob,
-
Bob follows Alice,
-
Carol follows both Alice and Bob,
-
Dave follows Alice.
Using our index followers_by_followee
, we can now answer questions
about these relationships.
To find a person’s follower list, we use a Select
query to extract the
ref
of each follower, like this:
Select("ref", Get(Match(Index("people_by_name"), "Alice")))
Find Alice’s followers
We can use a ref
as the term in a Match
query on our relationship
index. This query returns all of the refs of Alice’s followers:
Paginate(Match(Index("people_by_name"), "Alice"))
The result should look something like this (the identifiers for your documents are distinct):
{ data: [ Ref(Collection("people"), "236350059641307648") ] }
Our application might be able to interpret refs, but they’re hard for
a human to comprehend, so let’s Map
over the set of refs, get each
person’s document, and select their name out of the document.
Map(
Paginate(Match(Index("people_by_name"), "Alice")),
Lambda("person",
Select(["data", "name"], Get(Var("person")))
)
)
The result should be:
{ data: [ 'Alice' ] }
Putting it all together gives us a human-friendly list of the names of Alice’s followers:
Map(
Paginate(
Match(
Index("followers_by_followee"),
Select("ref", Get(Match(Index("people_by_name"), "Alice")))
)
),
Lambda("person",
Select(["data", "name"], Get(Var("person")))
)
)
The result should be similar to (the order might differ):
{ data: [ 'Bob', 'Dave', 'Carol' ] }
Find Alice’s OR Bob’s followers
Now that we’ve seen how to list a single person’s followers, we can use that knowledge to ask questions about the connections between follower lists.
For example, the union of the follower lists tells us who follows either person. Here, we ask who follows Alice or Bob:
Map(
Paginate(
Union(
Match(
Index("followers_by_followee"),
Select("ref", Get(Match(Index("people_by_name"), "Alice")))
),
Match(
Index("followers_by_followee"),
Select("ref", Get(Match(Index("people_by_name"), "Bob")))
)
)
),
Lambda("person",
Select(["data", "name"], Get(Var("person")))
)
)
The result should be similar to:
{ data: [ 'Alice', 'Bob', 'Dave', 'Carol' ] }
Find Alice’s AND Bob’s followers
The intersection of follower lists finds common followers among people. This query asks who follows Alice and Bob:
Map(
Paginate(
Intersection(
Match(
Index("followers_by_followee"),
Select("ref", Get(Match(Index("people_by_name"), "Alice")))
),
Match(
Index("followers_by_followee"),
Select("ref", Get(Match(Index("people_by_name"), "Bob")))
)
)
),
Lambda("person",
Select(["data", "name"], Get(Var("person")))
)
)
The result should be:
{ data: [ 'Carol' ] }
Find people who follow Alice, but NOT Bob
The difference of follower lists tells us who follows some people, but not others. This is how we ask for followers of Alice, but not Bob:
Map(
Paginate(
Difference(
Match(
Index("followers_by_followee"),
Select("ref", Get(Match(Index("people_by_name"), "Alice")))
),
Match(
Index("followers_by_followee"),
Select("ref", Get(Match(Index("people_by_name"), "Bob")))
)
)
),
Lambda("person",
Select(["data", "name"], Get(Var("person")))
)
)
The result should be similar to:
{ data: [ 'Bob', 'Dave' ] }