Twitter Follower System Data Model

Modeling the Twitter Follower system is one of the biggest problems with noSQL. I assume this could be done effortlessly in DGraph. The biggest problem, specifically, is querying all posts (tweets) by users you follow sorted by createdAt descending.

So, how could this query theoretically be done?


type User {
  id: ID!
  email: String!
  following: [User] @hasInverse(field: followers)
  followers: [User] @hasInverse(field: following)
  posts: [Post] @hasInverse(field: creator)
}

type Post {
  id: ID!
  name: String!
  description: String!
  createdAt: DateTime
  creator: User!
}

1.) Does this schema look correct?

2.) How would the query look (I guess forward and backward with @cascade) ?

a) query posts by all current user’s followers sorted by createdAt desc
b) query posts where user following equals current user sorted by createdAt desc

I am not quite sure how to do a) and b) assuming the schema is correct.

Thanks,

J

This is how it might look like in DQL. You could make this be called via GraphQL.

{
  var(func: uid(my-id)) {
    following {
      p as posts # Store all the posts created by your followers.
      @filter(gt(createdAt, "2021-04-07"))
      # Can optionally filter the posts by createdAt, to only pick posts within the last 7 days or so.
      # Basically, idea is to reduce the number of posts picked up.
    }
  }
  res(func: uid(p), orderdesc: createdAt, first: 100) {
    id
    name
  }
}

I don’t see where it queries a post at all here, but just Users type… Shouldn’t the word post be in there somewhere?

J

Ah… sorry. It should be posts instead of creator. I’m updating.

And don’t forget the GraphQL type dotted notation for the DQL query. :wink:

How would I make the GraphQL query? Unless I am using lambdas, I can’t call dql in cloud dgraph via GraphQL. Please correct me if I’m wrong, or there would be a security risk…

Users.following.posts ? I am so confused here…

Does this look correct?

query {
  queryUsers(id: $user) @cascade {
    following {
      posts(order: { desc: createdAt }, first: 10) {
        id
        title
        description
        createdAt
      }
    }
  }
}

And from the other direction:

query {
  queryUser @cascade {
    followers(id eq $user) {
      posts(order: { desc: createdAt }, first: 10) {
        id
        title
        description
        createdAt
      }
    }
  }
}

Trying to get a general grasps of this.

Thanks,

J

You can embed DQL query in the schema, and trigger it via GraphQL.

https://dgraph.io/docs/graphql/custom/dql/

Thanks @mrjn, good to know I can call DQL that way.

Does anyone know if my GraphQL queries look correct?

Thanks,

J

I believe they will work, but don’t think it is going to do exactly what you expect. And by that I mean that the ordering of the posts happens in each list for each user, but not an ordered list of all the posts together. To do that you would have to do DQL inside of GraphQL like Manish said.

Sorry for the confusion. Let me try to quickly explain.

Your GraphQL schema is not exactly the same as the DQL schema. There is some “magic” that the API does for you that you do not have to be aware of until you start doing some DQL queries. Because when you start using DQL, you must understand how the GraphQL schema gets mapped to the DQL schema. And that is done by “type dotting the predicates”.

That sounds somewhat complex but is actually really simple, especially if you are not using any interfaces.

The GraphQL schema you provided above gets mapped to the following DQL types (leaving out the predicate definitions themselves)

type User {
  User.email
  User.following
  User.followers
  User.posts
}
type Post {
  Post.name
  Post.description
  Post.createdAt
  Post.creator
}

Notice that User.id and Post.id was not present above. That is because they get translated to uid directly and are not type dotted renamed.

Here is a the correct DQL query for your GraphQL types that @mrjn tried above:

{
  var(func: uid(my-id)) { # replace my-id with your user id.
    User.following {
      p as User.posts # Store all the posts created by your followers.
      @filter(gt(Post.createdAt, "2021-04-07"))
      # Can optionally filter the posts by createdAt, to only pick posts within the last 7 days or so.
      # Basically, idea is to reduce the number of posts picked up.
    }
  }
  res(func: uid(p), orderdesc: Post.createdAt, first: 100) {
    id
    Post.name
  }
}

See hos that every field was translated to a dotted type?

@amaster507

Interesting.

Do you think there is some feature request here that would allow users to avoid DQL and get the expected results in GgraphQL?

I feel like this is a common use case.

J

The idea here is to extend the filters to also filter on edges which would give you what you wanted with a query such as: (this is not yet available)

query ($myID: ID!) {
  queryPost(
    filter: {
      creator: {
        or: [
          { following: { id: [$myID] } }
          { followers: { id: [$myID] } }
        ]
      }
    }
    sort: { createdAt: DESC }
    limit: 100
  ) {
    id
    name
    description
  }
}

The nested filters make sense, so +1 from me for another use case on the feature request.


However, I don’t understand your query. You’re saying get followers or following by the userId, which are two completely different things.

The goal here is to get the same results in two different ways (I am learning here, and obviously one way will be quicker than the other). Your query will mix the follower’s post and the following’s post, which doesn’t make sense to me…

What I would want (sorted by createdAt desc):

  1. Search by: get User.following.posts where User.following.id = $userId
  2. Search by: get User.followers.posts where User.id = $userID

I would think both of these would get the same results. It looks like the dql above is using the first option…

So, if I go the 2nd option, do I still need nested filtering?

J