A relationship is a connection between two entities of the data model. This connection allows the user to load entities starting from a root and select a projection of an entire graph, making data loading faster and easier.
In Typetta, relationships between entities are defined by adding references between one entity and another.
In Typetta, a relationship is defined by creating a reference from a field to the counterpart on each connected entity. It is possible, if desired, to also create mono-directional connections from one entity to another without having the inverse connection.
To define a reference to another entity, Typetta provides three different directives: @innerRef, @foreignRef and @relationEntityRef.
@ innerRef identifies a connection through a reference between a field of the source entity and the id of the target entity. Let’s take the following model as an example:
type User @entity @mongodb {
id: ID! @id
firstName: String
lastName: String
}
type Post @entity @mongodb {
id: ID! @id
userId: ID!
user: User! @innerRef
content: String!
}
In this case, the user field of the Post entity is a virtual field that is not stored directly in the data source inside the Post entity, but is subsequently populated by loading the user referenced by the adjacent userId field.
It is called @innerRef because the reference to the connected entities is inside the entity containing the relationship. By convention, this reference (in the example the userId field) must have the exact same name as the relation field with the suffix Id. Again by convention, the connected entity field that is referenced is the field annotated as @id. So, in this case, Post.userId refers to User.id.
Both of these configurations can still be modified and made explicit by the user to have more flexibility; see the more complex example below:
type User @entity @mongodb {
id: ID! @id
anotherId: ID!
firstName: String
lastName: String
}
type Post @entity @mongodb {
id: ID! @id
anotherUserId: ID!
user: User! @innerRef(refFrom: "anotherUserId", refTo: "anotherId")
content: String!
}
@foreignRef identifies a complementary connection to the previous @innerRef and is so named because the reference to the connected entities is the connected entities themselves and not in the entity containing the relationship. Let’s take as an example the previous model that we are going to enrich with a reference between the user and their posts:
type User @entity @mongodb {
id: ID! @id
firstName: String
lastName: String
posts: [Post!] @foreignRef(refFrom: "userId")
}
type Post @entity @mongodb {
id: ID! @id
userId: ID!
user: User! @innerRef
content: String!
}
Again in this case the posts field of the User entity is a virtual field that is not stored on the data source structure that represents the User entity, but is subsequently populated, on request, from another data source structure (another table or collection).
Here, unlike the case of @innerRef, it is always necessary to specify a refFrom parameter that identifies the connected entity field referencing the target entity identifier. In the example, Post.userId refers to User.id.
It is also possible to specify the refTo parameter to handle more complex cases, as demonstrated in the following example:
type User @entity @mongodb {
id: ID! @id
anotherId: ID!
firstName: String
lastName: String
posts: [Post!] @foreignRef(refFrom: "anotherUserId", refTo: "anotherId")
}
type Post @entity @mongodb {
id: ID! @id
anotherUserId: ID!
user: User! @innerRef(refFrom: "anotherUserId", refTo: "anotherId")
content: String!
}
Relationships with cardinality n-m are typically designed using a third entity that connects both other entities referencing them by id. Let’s assume a data model like the following:
type Post @entity @mongodb {
id: ID! @id
content: String!
categories: [Category!] @relationEntityRef(entity: "PostCategory")
}
type Category @entity @mongodb {
id: ID! @id
name: String!
}
type PostCategory @entity @mongodb {
id: ID! @id
postId: ID!
categoryId: ID!
}
Each post can have multiple categories and each category can have multiple posts. It would have been possible to create a @foreignRef between Post and PostCategory and a further @innerRef between PostCategory and Category, but doing so forced the data model to make explicit the presence of a purely connection structure (a MongoDB collection or SQL table) linked to the representation of the data on the database.
Thanks to the reference @relationEntityRef, the relationship Post.categories is much clearer and more transparent for the user. Note that this directive, like the previous ones, is based on a convention that fields within the connecting entity must have the same name as the connected entities, with a lower initial case, followed by Id.
As in the previous cases, however, each single reference can be made explicit:
type Post @entity @mongodb {
id: ID! @id
content: String!
categories: [Category!] @relationEntityRef(entity: "PostCategory", refThis: { refFrom: "idOfAPost" }, refOther: { refFrom: "idOfACategory" })
}
type Category @entity @mongodb {
id: ID! @id
name: String!
}
type PostCategory @entity @mongodb {
id: ID! @id
idOfAPost: ID!
idOfACategory: ID!
}
In the above example, as you can guess, PostCategory.idOfAPost refers to Post.id, while PostCategory.idOfACategory refers to Category.id.
In the above example, as you can guess, PostCategory.idOfAPost refers to Post.id, while PostCategory.idOfACategory refers to Category.id.
Using references as described in the previous sections, Typetta allows you to manage relations between entities with any type of cardinality and maximum flexibility. Some examples of relations 1-1, 1-n and n-m are shown below.
Below is an example of a 1-1 relation between a user and their profile:
type User @entity @mongodb {
id: ID! @id
firstName: String
lastName: String
profile: Profile @foreignRef(refFrom: "userId")
}
type Profile @entity @mongodb {
id: ID! @id
userId: ID!
user: User! @innerRef
language: String!
}
Below is an example of a 1-n relationship between a user and their posts:
type User @entity @mongodb {
id: ID! @id
firstName: String
lastName: String
posts: [Post!] @foreignRef(refFrom: "userId")
}
type Post @entity @mongodb {
id: ID! @id
userId: ID!
user: User! @innerRef
content: String!
}
Lastly, an example of an n-m relationship between posts and categories:
type Post @entity @mongodb {
id: ID! @id
content: String!
categories: [Category!] @relationEntityRef(entity: "PostCategory", refThis: { refFrom: "idOfAPost" }, refOther: { refFrom: "idOfACategory" })
}
type Category @entity @mongodb {
id: ID! @id
name: String!
}
type PostCategory @entity @mongodb {
id: ID! @id
idOfAPost: ID!
idOfACategory: ID!
}
A relationship can also connect an entity with itself and can be of any cardinality. Recursive relationships are handled in the same way as other relationships, through the @innerRef, @foreignReg and @relationEntityRef directives.
Here is an example of a recursive relationship with cardinality 1-1:
type User @entity @mongodb {
id: ID! @id
firstName: String
lastName: String
fatherId: ID!
father: User! @innerRef
}
With 1-n cardinality:
type User @entity @mongodb {
id: ID! @id
firstName: String
lastName: String
sonsId: [ID!]
sons: [User!] @innerRef
}
Anc with n-m cardinality using @relationEntityRef:
type User @entity @mongodb {
id: ID! @id
firstName: String
lastName: String
friends: [User!] @relationEntityRef(entity: "Friends", refThis: { refFrom: "from" }, refOther: { refFrom: "to" })
}
type Friends @entity @mongodb {
id: ID! @id
from: ID!
to: ID!
}