This feature request serves as central place to discuss how interface types and related operations could look like in Prisma.
Use Case: Add a User interface so multiple models can inherit from it and get authentication out of the box
Thanks @stevewpatterson, this would be useful! Another use case: interface for createdAt
/updatedAt
The File
model could be pulled out as an interface. If you have relations between File and multiple other models, you currently have many null
values.
Similarly, I want to create Users and Communities which have Slugs and the slug needs to be unique across all of them. Right now when linking User with Slug and Community with Slug, it creates two relations from Slug to Community and from Slug to User. But only exactly one of these relations should have data at any given time. I understand that interface types would fix this issue.
Another use case: implement a global permissions system across multiple types.
Here's a gist with an example schema:
https://gist.github.com/amonks/989c815eca601d7ae63ded8fa5f9d530
I don't know how to see notifications for gist comments, but I'm happy to answer questions here.
I'm working on a CRM style application and need either Union Types or Interfaces to model content. I would need a ContentElement interface that can be implemented by a TextNode or a ImageNode, for example so that I can then build the content of my page based on these different elements.
Hope this is coming really soon, as it is so essential for many more complex use cases.
Am I missing an obvious workaround? It seems like any non-trivial model would need this or unions. For example, take this: https://www.graph.cool/docs/faq/graphcool-relation-tag-in-idl-schemas-jor2ohy6uu/ and try to make it so Users can comment on either Posts or Users:
https://gist.github.com/brandf/c8b06dae80b0fd994f06838d69f5437f
+1 this feature request. Our use case is with regards to a Notification
model. So an interface would really be helpful to be able to create a relationship between Notification
model, and other types of models like Message
, Task
, Project
.
@marktani if the stage/wip label is being removed, is it being demoted in priority? Where can we expect it in the roadmap? waiting? I understand that there may be no specific timeline (#165) but a feeling of priority would help.
We see interfaces as filling a major requirement for our application and I'd be jumping at scaphold.io for their integration of interfaces if it didn't look like they were dying as a service. Not to play down many of the other killer things you folks are doing! :)
Is there just a difference in philosophy, like graph.cool would prefer users to strictly normalize the types and use relations to connect them back together? Thanks.
Hey @ptpaterson, thanks so much for your feedback! This matter absolutely did not decrease in priority and is on our near-future roadmap together with #165. We're in the process of changing the way we communicate and structure our roadmap, and the "stage" labels have become obsolete in the process.
You can now follow along our two-week roadmap in Github projects. The upcoming milestone 1.5 is tracked here: https://github.com/graphcool/graphcool/projects/3. Our immediate focus lies on getting the Resolver + CLI beta out to everyone, as together, they enable completely new workflows and cover _a lot_ of popular feature requests (https://github.com/graphcool/graphcool/issues/213, https://github.com/graphcool/graphcool/issues/39, ...).
Afterwards, I can see us to focus on raw API capabilities (like cascading deletes #47, for example) as well as schema primitives (interfaces, unions, relations, ...), but this is still in flux and remains to be seen ๐ Rest assured that you are definitely heard though, and I really appreciate your input.
@amonks I love the idea of using Interfaces to simplify permission management!
The other major use case is to have a relation to an interface and be able to use the regular relation filters.
There are several different ways to implement interface support on the database level. Each implementation represents a different set of tradeoffs and it is not obvious that one of them is best in all cases. Providing detailed use cases will help us implement interfaces in a way that supports the most use cases.
If you have a use case for interfaces, please take the time to describe it here :-)
I'll list different implementations here so we can can start a more detailed discussion.
Consider the following data model:
interface User {
id: String
name: String
}
type Customer implements User {
customerNumber: Int
}
type Employee implements User {
department: String
}
A single table called User contains fields from the User interface as well as both Customer and Employee:
User
Each concrete type has a dedicated table with both interface fields and type fields:
Customer
customerNumber
Employee
Fields on the interface are in a separate table:
User
name
Customer
customerNumber
Employee
department
I only after initially posting here, I discovered that Amazon Aurora is sitting on the back-end. The implementation that I am working on has been focusing on some tricks that No-SQL makes easy. I essentially need an entity-attribute-value model so that the user can create arbitrary connections to all sorts of arbitrary data. SQL is not kind to my data, but JSON is nice.
Understanding the backend a bit more makes me skeptical we can get this running on graphcool and still achieve the efficiency and flexibility of configuration we are shooting for.
Maybe custom database is the first feature I need to look out for :) I'd love to still go through the exercise, though.
Here is the blog-post worthy description of my use case.
Thanks!
Thanks Paul!
I think a helpful exercise for you would be to define all query paths to better understand where your performance constraint will be.
It might turn out that you will be able to store your flexible configuration in a Json
field together with a few values that are required for filtering.
If defining a few fields required for filtering is not possible, maybe allowing filtering on data in a Json
field is really what you want: https://github.com/graphcool/graphcool/issues/148 This will allow Graphcool to be used in a similar way as MongoDB, but you loose some of the benefits of the GraphQL type system.
Please let define in interface
various directives and all types which are implementing that interface should inherit all directives. For example:
interface Product @model {
id: ID! @isUnique
vendor: Vendor! @relation(name: "VendorProducts")
orders: [Order!]! @relation(name: "OrderProducts")
name: String!
description: String
slug: String! @isUnique
price: Float! @relation(name: "ProductPrice")
images: [Image!]! @relation(name: "ProductImages")
belongsToCollections: [Collection!] @relation(name: "CollectionProducts")
isPublished: Boolean! @defaultValue(value: false)
materialInfo: MaterialInfo!
colorInfo: ColorInfo!
createdAt: DateTime!
updatedAt: DateTime!
}
All types implementing interface Product
should be @model
types, all isPublished
fields in those types should have @defaultValue(value: false)
, all images
fields should have @relation(name: "ProductImages")
etc.
@ptpaterson GraphQL is all about a well-defined schema, that can be introspected, and known on the client. You could of course come up with a schema that's generic enough for you to store the information in.
An alternative would be to dynamically create Types and fields based on the needs of the user. I did a small POC with that. Bottom line: if you dynamically create your queries this works, but Relay is out the door, as well as most of Apollo's store management, unless you use middleware to translate your dynamic query results into a generic structure that you can use for your client side store.
So either you have a very generic server, with server-side performance impact, or you have a dynamic server, with client-side performance impact. However, the client-side strategy corresponds mostly to the issues you would also have with that using No-SQL.
The alternative that actually serves you best would be using a Graph database, as it is fundamentally different in the way the the node itself defines the type, not some underlying type definition (much like no-SQL), but querying it is still a walk in the park (using the right query language). As much as I hate advising people _not_ to use GraphQL, it's not a one solution fits all kind of thing, much like any other technology out there.
Interface types still allow the use of well-defined schema, they just need to be used differently.
I do agree that the data I have would be best served with a graph database, or at least modeled as a graph, which could be done in a relational database or document store. But in order to do that effectively, I either need a schema-less solution or interfaces. 'Effectively' is the key, because I can already use Graphcool to model vertex types and edge types. It's just that each vertex type must explicitly declare separate collections of each possible edge type in advance. If there were interface types then each vertex type could have a collection of abstract edge types, that could then point to another abstract vertex type.
A native graph database doesn't preclude one from using GraphQL. Trying to traverse an abstracted graph like this with GraphQL is imperfect, because GraphQL is indeed not perfect for all solutions, but it's not impossible.
The things that we get out of Graphcool that aren't just Graphql... instant setup, permissions, integrations, serverless setup... to me are worth trying to make work. Unless I have been missing something, the graph database world is seriously lacking in this department - in bootstrapping up a full production application environment. So I can deal with _modeling_ as a graph as opposed to using a native graph db. But I don't see a way to do it without interfaces.
@ptpaterson A workaround for using interfaces would be do define the Interface Type as a 'normal' Type, and add a 'concreteType' enum field. Your queries will become a bit more verbose, because you need to filter the client collection based on the concreteType field, but at least you can save it.
Also, if you can define your Types using Interfaces, it's way less dynamic than your original post made it seem. My answer was based on really arbitrary Types, not known at design-time.
A workaround for using interfaces would be do define the Interface Type as a 'normal' Type, and add a 'concreteType' enum field.
@kbrandwijk Can you post an example of this that works with the current version of Graphcool?
I assume @kbrandwijk is talking about an approach similar to this one: https://www.graph.cool/forum/t/schema-building-with-different-user-types/272/2?u=nilan
Ok, I see how we can construct our schema using that approach. Can you show the query and mutation pieces of that pattern? Should I use a nested mutation to insert and update? I'd also like to see the correct way to query data that's structured that way. Do I need to use @include
?
It's easy to add interfaces to an existing Graphcool endpoint using the API Gateway pattern. I have created an example here: https://github.com/kbrandwijk/graphcool-gateway-examples/tree/master/interfaces
I think the API Gateway example shows a valuable lesson of how this could be implemented. The Gateway pattern works like this:
Each concrete type (implementation of interface) acts just like a normal type (so has it's own table like any other type)
The only difference is on relations. There we benefit from globally unique id's, so there's never a conflict in a collection. The only change I think that is needed is that for queries, the database query should be a join over all concrete types.
Mutations also support Interfaces. Here, the same globally unique id's make sure that you can just have 1 id field and one Type field. For vehicleId
, the id could be looked up in both concrete Types, and for vehicle
, the resolveType
should be able to determine the concrete Type. An alternative would be adding a Type field for every concrete Type.
For example:
interface Vehicle { numberOfWheels: Int }
type Car implements Vehicle {
numberOfWheels: Int
horsePower: Int
}
type Bike implements Vehicle {
numberOfWheels: Int
color: String
}
type User {
vehicle: Vehicle
}
Now the createUser
mutation would look like:
createUser(id: ID!, vehicleId: ID, vehicle: Vehicle)
or
createUser(id: ID!, vehicleId: ID, car: Car, bike: Bike)
Thanks Kim!
Given your data model, Graphcool should support the following queries:
โ
allUsers(filter: {vehicle: {numberOfWheels: 4}}) { vehicle { ... on Car { .horsePower } } }
filter on field on interface
โ๏ธ allUsers(filter: {vehicle: {horsePower: 42}}) { vehicle { ... on Car { horsePower } } }
filter on field not on interface
โ
allUsers(filter: {vehicle: {Car: {horsePower: 4}}}) { vehicle { ... on Car { horsePower } } }
filter on field on concrete type. This will only return cars
โ
allUsers(filter: {vehicle: {OR: {Car: {horsePower: 4}, Bike: {}}}}) { vehicle { ... on Car { horsePower } } }
filter on field on concrete type. The empty Bike filter will return all bikes
โ
allCars(filter: {horsePower: 42, numberOfWheels}) { horsePower }
normal allX query
โ
allVehicles(filter: {numberOfWheels: 4, Car: {horsePower: 42}}) { numberOfWheels ... on Car { horsePower } }
allX query for interface
โ
allVehicles(filter: {numberOfWheels: 4, Car: {numberOfWheels: 42}}) { numberOfWheels }
filters on interface fields can be specified on the interface and concrete type level. If both are present, they must match
For create mutations, I think your second option is the way to go:
createUser(id: ID!, vehicleId: ID, car: Car, bike: Bike)
We should return an error if more than one of vehicleId
, car
, bike
is present.
filter: { type: 'Bike' }
. That's easier to build with variables.numberOfWheels
without value in the allX
query.Also, how would create mutations look when vehicle: Vehicle
is vehicles: [Vehicles!]!
?
And how would the relation attributes look (given you decide to keep them, I created another issue to remove them completely, but I can't find it).
Type can be inferred, so I think 'filter on field not on interface' could also work.
I don't think this is a good idea. Different types can have the same field without it being part of the interface
Your second point is valid though, and I need to put some more thought into how best to narrow a query to specific types. In my suggestion above, adding a type specific filter would return only nodes of that type. Adding other type filters with an empty filter is a way to add more types, but I don't think that is clear enough.
I think it's not relevant whether a field is part of the interface or not:
allUsers(filter: {vehicle: {numberOfWheels: 4}}) {...}
allUsers(filter: {vehicle: {horsePower: 42}}) {...}
Why should one work, and not the other?
Can allUsers(filter: {vehicle: {horsePower: 42}}) {...}
return bikes?
No, but allUsers(filter: {vehicle: {numberOfWheels: 4}}) {...}
also won't return bikes.
So, how does the VehicleFilter
look like?
type VehicleFilter {
id: ID # plus variants like id_contains ...
horsePower: Int
color: String
}
What about this:
interface Vehicle { numberOfWheels: Int }
type Car implements Vehicle {
numberOfWheels: Int
horsePower: Int
weight: Float
}
type Bike implements Vehicle {
numberOfWheels: Int
color: String
weight: Int
}
type User {
vehicle: Vehicle
}
Note weight: Int
and weight: Float
.
Meh... You win ๐
Back to: allUsers(filter: {vehicle: {Car: {horsePower: 42}}})
then for fields that are not part of the interface.
I do think that interface fields should also exist on the vehicle.Car
type, to filter cars only.
type VehicleFilter {
id: ID
numberOfWheels: Int
Car: VehicleCarFilter
Bike: VehicleBikeFilter
type: [String]
}
type VehicleCarFilter {
id: ID
numberOfWheels: Int
horsePower: Float
}
type VehicleBikeFilter {
id: ID
numberOfWheels: Int
color: String
}
I do think that interface fields should also exist on the vehicle.Car type, to filter cars only.
I agree with this. And then we should return a runtime error if two different values are provided (or maybe if two values are provided period.)
Why would that cause a runtime error. It would filter all concrete Types on the top-level field, and a specific concrete Type on the specific field.
Update: I just read back that sentence aloud, and it makes no sense...
allUsers(filter: {vehicle: {numberofWheels: 4, Car: {numberOfWheels: 6}}})
Should give you all concrete Type nodes with 4 wheels, and only Cars with 6 wheels.
looking forward!
We already discussed different approaches for filtering in detail.
What would different approaches for ordering a field of [Vehicle!]!
look like?
I think the most common use case is to order like this: vehicles(order_by: numberOfWheels_ASC)
.
vehicles(order_By: numberOfWheels_ASC)
for interface fields.
vehicles(order_By: { Car: horsePower_ASC })
for type fields?
vehicles(order_By: { Car: horsePower_ASC }) for type fields?
Why would we need ordering
for type fields? Couldn't we just say then
allCars(orderBy: hrosePower_ASC) { horsePower }
Are relational fields allowed on interfaces?
One example where this would be super useful is an "Owned" interface that comes with an owner: User!
field. I could then query the owner of any nodes of models that implement this interface like so:
query {
owneds(where: {id: "id"}) {
owner {
id
}
}
}
If relational fields aren't allowed then interfaces will become far less useful for basic things e.g. having a "liked" field for a user that can contain both posts and comments.
Is there a roadmap regarding the implementation of interfaces? Any information on when it might be available? As mentioned before it is crucial for more complex applications to have this feature.
any good news?
Does anyone have a good workaround for this currently? My team's application has multiple different types of users, all who use the API in a completely different manner. These users all need to be able to communicate with each other through our shared messaging API. In our previous setup, Message had a property author: User!
, in which User was an interface implemented by multiple user types. We are making the transition to Prisma as it perfectly solves some problems we were facing, however this is just one of the many use cases in our application for interfaces, so we cannot finish our transition until a good solution has been found.
For a temporary solution, I'm tempted to do one of these two things:
author: User!
property to author: String!
, in which the string stores the id of the author. This however would require another property authorType: String!
to be added, otherwise it would be impossible to know the correct user type. This would also require another round trip to the server to query the correct user object once the user type is known.User
type with a new property __type: String!
, which can be used to determine the correct business logic to use for each specific user type. This doesn't work for our use case, as only one database table would be used for all user types, causing conflicts with some of our required fields.UserUnion
type, however this time we only use that UserUnion
type for any property which requires the union of all user types. This way, each user type still gets its own database table, but the resolver can return a pseudo Union type.Although the third option will most likely work for us, it seems messy and I'm worried about migrating the data back to an interface structure once Prisma allows it. However, it seems the roadmap for implementing interface types (and Union types) is unclear, so my team cannot afford to wait. If anyone has a more elegant solution, I would love some feedback.
@marktani is there any update on timeline for this? Where can I find your roadmap? Seems like interfaces are a pretty huge need to me... much like union types.
Where does this stand? I'm designing a real estate project. So I could use something like this. I have different property types. I have residential and commercial for example. They have different fields requirements.
Thanks a lot for your feedback everyone!
@adamjking3, a similar discussion has started here: https://www.graph.cool/forum/t/feed-with-multiple-types-interfaces-unions-workaround/2450?u=nilan. I think your input there would be very valuable.
@couturecraigj @Jscott388, we are currently working on a public roadmap that gives you an insight into what we're currently working at. You can follow our progress with this here: #2142. Please leave an upvote & subscribe to get notified :slightly_smiling_face:
Suggesting a workaround for this at https://medium.com/@tibotiber/graphql-interfaces-and-union-types-with-prisma-and-yoga-7224f9e1d9ad. Happy to get any feedback.
@marktani - any update on this issue? We would love to be able to push forward with Prisma, but this is our primary blocker at the moment. Seems like this request has been on the table for over a year, and the public roadmap from #2142 has no content at all, so it's a bit hard to hold out hope at the moment...
@marktani - Just wondering if there any active development on this issue. Any indication if this will be fixed or not, will help us a lot.
+1 for an update or ETA. I know you're super busy with a bunch of stuff, but it's been almost 2 months now since the last team update on this issue. Also +1 for @adamjking3 's use case, where users can have more than one role, and interfaces would be a nice way of achieving this. A simple admin-boolean or even a user enum doesn't solve more complex requirements
+1 ETA
Starting to build a database using a workaround now, but there is a lot of verbosity... And there is no way to properly enforce required fields
While there is no concrete update yet for the timeline of this feature, it's definitely high on our priority list.
Also, as mentioned in #2114, I think inheritance is a really interesting concept to consider as well for data modeling when implementing interfaces & type polymorphism.
Anyone working on this issue? Really need it for some higher level architecturing of the app.
It would be very helpful for us to see some examples of the data model you'd like to design (assuming it would work). Having many different examples helps us to better design the capabilities and syntax of this feature.
cc @viztor @FrankSandqvist @jhalborg @rohitghatol ...
Gonna copy&paste an example from one of the earlier comments:
interface Vehicle { numberOfWheels: Int }
type Car implements Vehicle {
numberOfWheels: Int
horsePower: Int
}
type Bike implements Vehicle {
numberOfWheels: Int
color: String
}
type User {
vehicle: Vehicle
}
@schickling a prisma pseudo-schema of a virtual application might be
interface Transactable {
id: ID @unique
transactions: [Transactions!]!
}
interface Transactions {
id: ID @unique
subject: Transactable!
}
type TransferOwnership implements Transactions {
id: ID @unique
}
type Car implements Transactable {
id: ID @unique
transactions: [Transactions!]!
}
example of the schema I'm using:
interface Conviction {
id: ID!
fromUpdate: BucketUpdate!
row: Int!
assetClass: AssetClass!
isin: String!
ticker: String
name: String!
inceptionDate: String
prr: Int!
ltv: Int!
archived: Boolean!
restricted: Boolean!
}
type Equity implements Conviction {
# interface
id: ID!
fromUpdate: BucketUpdate!
row: Int!
assetClass: AssetClass!
isin: String!
ticker: String!
name: String!
inceptionDate: String!
prr: Int!
ltv: Int!
archived: Boolean!
restricted: Boolean!
# custom fields
threePtUpdateLink: String
stockReportsLink: String
tradeNotesLink: String
threePtUpdate: String
}
type Fund implements Conviction {
# interface
id: ID!
fromUpdate: BucketUpdate!
row: Int!
assetClass: AssetClass!
isin: String!
ticker: String
name: String!
inceptionDate: String
prr: Int!
ltv: Int!
archived: Boolean!
restricted: Boolean!
# custom fields
factsheetLink: String
}
type Bond implements Conviction {
# interface
id: ID!
fromUpdate: BucketUpdate!
row: Int!
assetClass: AssetClass!
isin: String!
ticker: String
name: String!
inceptionDate: String!
prr: Int!
ltv: Int!
archived: Boolean!
restricted: Boolean!
# custom fields
tradeNotesLink: String
threePtUpdate: String
coco: Boolean
}
My team has need of this to standardize a few things throughout our data model:
Our ideal SDL would look something like this:
type Attribute {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!
name: String!
}
type AttributeValue {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!
attribute: Attribute!
value: String!
}
interface Common {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!
attributes: [AttributeValue!]!
}
type Customer implements Common {
# Includes all fields from the interface plus the field below
name: String!
}
So, the "Customer" type would get expanded to:
type Customer implements Common {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!
attributes: [AttributeValue!]!
name: String!
}
Another use case:
A timeline for a user, where a TimelineEvent
can be one of multiple types, i.e. a UserComment
, UserUpload
, UserStatusChanged
and so forth, so that a client can show a paginated view of TimelineEvents
, showing a different view component based on the implementation of the TimelineEvent
My use case is in a chat application where a Message
has a Payload
which can be of a concrete type TextPayload
or ImagePayload
and other payload types that Iโll add later on.
My use case :
We have a video editing app where "cues" can be placed along a timeline. These cues have a reference to "contents", and these contents may be of different types, with different fields: Movie, Music, Bio...
I have just published a spec that touches on this topic. We would love to hear your feedback on this one.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 10 days if no further activity occurs. Thank you for your contributions.
Period: interface for startDate
/endDate
Any update on this?
@schickling, I see in your FAQ for Datamodel 1.1 that Polymorphic relations will not be part of Datamodel 1.1, and will be addressed later. Could you inform this thread as to where you see it in your priority list? Is this something you will accomplish this year? Next quarter? etc.
I think many of us on this thread will find it helpful to properly plan ahead, and perhaps implement a work-around before a stable version is released.
@ianjanicki - we are currently working on large architectural improvements to the Prisma query engine. These improvements will bring a number of benefits, including better performance and an architecture that support advanced features such as Polymorphic Relations. We expect this work to complete some time in Q3.
Datamodel 1.1 is a small intermediate step that prepares us for the real jump that will happen later this year.
Polymorphic Relations continue to be a high priority feature for us, but I can't give you a more precise timeline.
We recently introduced a roadmap that details our ongoing work. We update this roadmap with every release (2 week cycles).
Thanks for remaining a passionate Prisma user!
Hi Guys, So I tried implementing an interface that had some basic fields that need to be implemented by some type but when I implemented that interface on some type, it didn't restrict the type to have all the fields of interface and I was able to skip few interface fields and deploy. I want to know is this issue related to Prisma or I am doing something wrong. Thanks in advance.
Hey @marktani Is there any tentative date when this issue will roll out. It's kind of important for us as we are using Prisma for enterprise level application and without interface we are facing problems with Prisma.
After playing around with GraphQL and Prisma for a while, Im really having a hard time figuring out how can Prisma be used in any moderately production grade scenario.
it works really fine with online tutorials and such - but the no interface/union support seems to me like a huge blocker. It seems like Ill have to drop Prisma, and thats a pity - since it is great in any other way.
I would love to hear if anyone managed to actually bypass this issue with a complex schema. It will probably save me (and others) a lot of time if you did.
(unless ofcourse this issue will be addressed ;)
@ynvb Prisma1 is in maintenance mode so I wouldn't expect this to be added. ;)
Effort is focussed on Prisma2, it might be good to ask there or ask around in the Prisma community slack.
@ynvb this project was boasting it's wonders way before it can be called proper, imho. i tried hard using prisma1 on a given codebase, but now consider it to be the worst architectural choice i've made over some 10y. hope prisma2 is being managed... differently. good luck.
Hey @ynvb ๐ as @beeman pointed out correctly, Prisma 1 is in maintenance mode and we therefore won't add any new features to it. However, we already have a similar feature request for Prisma 2.0 here. It would be great if you could share more context about your exact use cases and requirements (what would you like to model with these features), so we can factor that in once we get to work designing and implementing that feature for Prisma 2.0! ๐
Did you actually already look into Prisma 2.0? I'd love to hear your thoughts on it! ๐
Most helpful comment
@marktani - Just wondering if there any active development on this issue. Any indication if this will be fixed or not, will help us a lot.