GraphQL schema relationship for embedded type (1 way relationship vs 2 ways)

I’m not so sure about how to build my GraphQL schema relationship.

An org can have many members, each member is a virtual type (@embedded) which defines the user and the role it has within the org.

What I want to do is:

  1. List all the members of an org.
  2. List all the orgs a member belongs to.

The 1) is straightforward.
The 2) is a bit harder, I’m not sure if I should use an index, or store the information as User.orgs, within the User entity too (2 ways, but data duplication)

enum OrgMemberRole{
  admin
  editor
}
type Org @collection(
  name: "Orgs"
){
  id: ID!
  members: [OrgMember]!
}
type OrgMember @embedded{
  user: User!
  role: OrgMemberRole!
}
type User @collection(
  name: "Users"
){
  id: ID!

  """TODO Is something like this necessary?"""
  orgs: [Org] @relation(
    name: "userOrgs"
  )
}

Any reason why you would not use a one-to-many relationship instead of @embedded between org and members?

I don’t need those as a distinct collection/docs, they’re perfectly fine embedded as JSON within the Users or Orgs collections. Why would I complicate things by creating another collection? It doesn’t bring any benefits AFAIK.

I think the benefit is that you get 2-way relationships out of the box with GraphQL.

If you REALLY need to optimize reads then I suppose duplicating can be helpful. But if you want to do that, then you will need to create few custom resolvers to create and update Users and Orgs. And behind it all you’ll likely end up with custom indexes with bindings to sorta lift the Org.members.user and Org.members.role to create relationships and ABAC Roles.

I uploaded this schema:

enum OrgMemberRole{
  admin
  editor
}

type Org @collection(name: "Orgs") {
  # id: ID! # removed because this is redundant.  Document ID's are built in.
  members: [OrgMemberLink]! @relation
}

type OrgMemberLink {
  user: User! @relation
  org: Org! @relation
  role: OrgMemberRole!
}

type User @collection(name: "Users") {
  # id: ID! # removed because this is redundant.  Document ID's are built in.
  orgs: [OrgMemberLink]! @relation
}

note I took out the extra id fields. You might actually have some separate business IDs to be worried about, but I see lots of people think they need to add them. If you want them, that’s ok too.

And could immediately do this to create an org an many members:

Connect some users to another new or existing org:

And check out which orgs a member is a part of:

2 Likes

Thanks for the hint on id fields, I thought it was needed indeed, I’m not sure what it’s supposed to do. But now that I think back of it, that’s probably why I was misleading when querying my schema expecting an id to be available while there wasn’t any. It’s actually an error which has been bothering me for a few weeks and I thought it was a Fauna’s bug.

Now I understand specifying id: ID! serves no purpose and does no good. The built-in “id” is available under _id, not id, and that’s what should be used.

I think I thought of things that way because the field in the FQL in named id.


I do not need to optimize reads, I’m fine with no data duplication as long as I can do what I want efficiently, and simply.

I see you decided to go for a OrgMemberLink collection, instead of embedding an array of OrgMember within the org like what I did. I was confused as to when should @embeddedbe used and I think I can extrapolate from your reply: They should be used only when not manipulating relationships.

Having an intermediate table there is fine from a design standpoint, it’s exactly what I’d do using SQL. I somehow thought it might be smarter to use an @embedded type, but it doesn’t seem so smart after all.

So, @embedded types are great to specify/design reusable object (or arrays of objects) that do not contain references to other entities. Does this definition make sense to you?

I’m confused about the ! in members: [OrgMemberLink]! @relation. Does that mean the Org cannot be created if there are no members?

If so, what’s the workflow? Feels like a “chicken & the egg” problem, do you first create the Org or the User? Can you create both at the same time and link them automatically?

nope. This is fine:

image

The required type, [OrgMemberLink]!, just means that when you query it that GraphQL has to send you back a non null value. It can certainly give back an empty array!

If you’re also just getting familiar with GraphQL at the same time as learning Fauna’s special flavor of it, then don’t miss out on the general GraphQL stuff. https://graphql.org/learn/schema/#lists-and-non-null

1 Like

In order to create the links, you can go in many directions:

  • Create Orgs. Create Users. Update Orgs to “connect” the Users.
  • Create Orgs. Create Users. Update Users to “connect” the Orgs.
  • Create Orgs and use nested mutation to “connect” existing Users.
  • Create Orgs and use nested mutation to “create” new Users.
  • Create Users and use nested mutation to “connect” existing Orgs.
  • Create Users and use nested mutation to “create” new Orgs.

The two big mutations I shared in the previous reply demonstrate both nested “create” and “connect”.

Check out the docs from the playground for what the relation input types look like

image

And please read docs for Relational Mutations:

Okay, my assumption was that using ! would affect writes, but it only affects reads? I don’t think so, because when I try to mutate without providing a required field of type String!, it fails.

So, the reason why it allows creating the members: [OrgMemberLink]! @relation is because it’s not null but an empty array. That’s what confused me. Thanks!

There is a “preview” for partial updates, which makes all the input type fields no longer Required types.