* Which Category is your question related to? *
graphql-transformer
Quick question: I'm curious if the transformer supports interfaces as documented here: https://docs.aws.amazon.com/appsync/latest/devguide/interfaces-and-unions.html
Currently creating the schema for my app, so I guess I will find out soon! :)
GraphQL with interfaces compiled with no issues so closing this for now.
Actually, based on the example at https://docs.aws.amazon.com/appsync/latest/devguide/interfaces-and-unions.html, how would you use graphql transformer to model this?
How I'd think of it is that there would be one "Event" table and so that would be the actual "@model" and different types of objects in it as follows. Since you're using "__typename" in the db table to distinguish which type of object and item is, then this is akin to single table inheritance in the RDBMS world. Correct? But doesn't look like amplify cli supports this since I get this error when I run the below schema:
Schema Errors:
Directive "model" may not be used on INTERFACE.
GraphQL request (13:16)
12:
13: interface Event @model {
^
interface Event @model {
id: ID!
name : String!
startsAt: String
endsAt: String
venue: Venue
minAgeRestriction: Int
}
type Concert implements Event {
id: ID!
name: String!
startsAt: String
endsAt: String
venue: Venue
minAgeRestriction: Int
performingBand: String
}
type Festival implements Event {
id: ID!
name: String!
startsAt: String
endsAt: String
venue: Venue
minAgeRestriction: Int
performers: [String]
}
type Conference implements Event {
id: ID!
name: String!
startsAt: String
endsAt: String
venue: Venue
minAgeRestriction: Int
speakers: [String]
workshops: [String]
}
It doesn't make sense to mark Conference, Festival, and Concert here as "@model" because then 3 tables would be created and the whole point of "interface" would be lost. But please correct me if I am wrong...
Hey these are all interesting points. The short answer is that the transform allows you to define interfaces and use them however you want but it does not support @model directives on interface types. This could 100% be a feature request that when using @model on an interface type, then we will store all object types that implement that interface in the same table using the __typename as the hash key and some uuid as the sort key but this has implications (esp. around how @connection would work) that should be discussed.
"the whole point of "interface" would be lost."
I don't agree with this as the point of interfaces is to express that multiple object types share a common set of attributes at the API level. This makes no implication about where or how your data is stored and thus it is really irrelevant if its 3 tables or 1. In your particular use case you want 1 table, but someone else might want 3 and both are totally reasonable uses of interface types.
Hi @mikeparisstuff thanks for the detailed response.
Fair point that in some cases people might want 3 tables instead of 1. And now with your support of custom queries and planned support for custom resolvers I think one would be able to write a query to get all Events even if they are stored in 3 different tables.
My particular use case is more of where the interface is a "User" type and the particular implementations can be something like "Doctor", "Patient", and "Staff" where these users can all do things like login but have very different views and permissions in the app.
I think it would make sense to have all Users, regardless of type, be in one table. You can argue I can use Cognito groups to differentiate between users but I think my functionality here goes beyond permissions and more of also the type of data stored for each user type and the relationships they have with other models in the system. Also DynamoDB's NoSql guide recommends to minimize the number of tables so this seems like a good case where only one table is needed rather than 3.
I am interested in what implications you are hinting at around how @connection would work if I go with the above plan. Any pitfalls I should be aware of or best practice recommendations would be appreciated. Currently I am only using the graphql transformer to see how amplify does things and my real appsync setup is done by hand since we need custom resolvers and ability to connect to a dynamo database and lambda methods in a different region than where appsync is hosted.
This makes sense. Unfortunately, I'm not sure this is going to be supported in the transform out of the box yet but you could write your own transformer that does just this. You can read more about how to do that here: https://github.com/aws-amplify/amplify-cli/blob/master/how-to-write-a-transformer.md
@mikeparisstuff Adding a type where @model on an interface would be preferred, using the model from @hisham
type Ticket @model {
id: ID!
event: Event @connection
}
I believe that this is related so I'm asking it here but I can open another question if that would be more appropriate.
I am trying to create a connection between two tables. I would like the child table to contain different types of objects that implement a similar interface. This issue indicates that I can't keep all of the child types in the same table. I tried to keep them in separate tables but then I don't think that @connection
will support that.
I tried something similar to the following:
type Container @model {
...
things: [Thing] @connection(name: "Things")
}
interface Thing {
...
container: Container!
}
type Thing1 implements Thing @model {
...
container: Container! @connection(name: "Things")
}
type Thing2 implements Thing @model {
...
container: Container! @connection(name: "Things")
}
I get "InvalidDirectiveError: Could not find an object type named Thing". I think that the right way to solve this is to allow multiple types in one table rather than allowing @connection
to reference multiple tables. Thoughts?
I'm needing to do the same thing as Erik. Basically, a many-to-many relationship between a type
and an interface
.
Related: #91
Also just ran into this error –– sounds like the @model
transformer on interfaces could be a great addition to the CLI
Really need interface to support @model and the implementing types to use a single table. For example if we have a type of Post and a TextPost or ImagePost I would like all of the Posts to be contained in a single table.
Also interested in interface support. My scenario involves a 'FieldType' interface in which they all share the same DynamoDB table. I've implemented this through my own resolvers, but when CLI v3 came out I found myself going back and updating all of the custom resolver logic to mirror the new @auth directive enhancements.
+1 for feature
My scenario involves a single "Person" type but variations (e.g. Singer, Dancer etc.) where each may have a specific relation to another object. BUT I would like all "_persons_" to be stored in a single table. The rationale is that a person - say Jane - can be a singer as well as dancer I just need one "person" object stored with different tags/aspects.
@hisham would you, please, share with us how you implemented the interface @model
behaviour?
I wrote a node script that modifies the cloudformation json files amplify cli generates (e.g. ConnectionStack.json and the model stack jsons). This script is run before any amplify push. Let's say if you have interface that 3 models implement. Amplify CLI will want to create 3 tables and data sources for these. So my script creates 1 table as the data source and connects the 3 models to that data source.
It's a temporary workaround, but has been running fine and survived various amplify cli updates. Hopefully an official solution comes out at some point.
@hisham could you, maybe, share your script on https://gist.github.com/? I would really appreciate it. I have the same use case and thought about using the ENUM
to differentiate between different user types.
Btw. do you use amplify mock
and if you do, does the script also creates 1 table locally?
Thank you very much in advance!!!
Sure here you go: https://gist.github.com/hisham/93ac6fcbe66f4a346d32681dc83e5ce2/revisions
The part that is not in the gist is you'll have to create a User.json and UserTable.json nested cloudformation stack (for the table you want the models to actually be stored in + appsync connections). These you will have to create manually through a custom nested stack, which amplify cli supports. They are similar tables and setup to what amplify cli creates for @model decorated types so basically copy the json amplify cli creates for one of your models that implement the interface and change some names in it to what your interface is called.
Now that amplify cli supports custom transformers, there's probably a better solution out there via graphql transformers. Would be nice if someone builds that at some point! :)
@hisham Thank you very much 👍
No problem. To answer your other question, I haven't tried amplify mock yet for appsync. I did try it once or twice but ran into an issue and never looked into it deeply. It's on my todo list. I don't think it's related to my script.
Sure here you go: https://gist.github.com/hisham/93ac6fcbe66f4a346d32681dc83e5ce2/revisions
The part that is not in the gist is you'll have to create a User.json and UserTable.json nested cloudformation stack (for the table you want the models to actually be stored in + appsync connections). These you will have to create manually through a custom nested stack, which amplify cli supports. They are similar tables and setup to what amplify cli creates for @model decorated types so basically copy the json amplify cli creates for one of your models that implement the interface and change some names in it to what your interface is called.
Now that amplify cli supports custom transformers, there's probably a better solution out there via graphql transformers. Would be nice if someone builds that at some point! :)
Thank you very much @hisham , this is exactly what we are looking for but we are struggling to reproduce this technique. We have not been successful in correctly recreating the "ConnectionStack.json" file referenced in your "modifyAmplifyCF.js" as we were expecting to see @connection used somewhere in the example in the header:
interface User {
id: ID!
email: AWSEmail!
}
type Doctor implements User @model {
id: ID!
email: AWSEmail
licenseNumber: Int
}
type Patient implements User @model {
id: ID!
email: AWSEmail
healthcardNumber: Int
}
Additionally, we are still trying to figure out what should be in the different stacks ("User.json", "UserTable.json", "Doctor.json", and "Patient.json") as we hypothesized that "UserTable.json" would be equivalent to an amplify created @model template, but that significant changes would be necessary to produce a "User.json" interface as well as "Doctor.json" and "Patient.json" interface implementing types belonging to the "UserTable".
If you have time and if you are comfortable with sharing this information, we would greatly appreciate it if you could share:
We greatly appreciate any help, and we are very grateful that you have found a workaround for this issue as many sleepless nights have led us here.
To provide a little more context, our overall objective is to produce a single DynamoDB table for our application with a similar structure using an interface (or ideally a Union but we have been unable to find working examples utilizing amplify), and then build batch mutation, query, and getitem pipeline resolvers to satisfy our access patterns.
Hi @wtrevena it's been a while since I implemented this so it's not fresh on my mind and I'm unable to share the source.
You're right that the example I provided does not have a @connection in the model. I guess if your models have connections (for example, a doctor can be connected to a prescriptions table), then you would have a ConnectionStack.json.
But if your model does not have any @connections then I guess no need to modify a Connection Stack.
Doctor.json and Patient.json are auto-generated and would be in your build/stacks folder. You would take one of these, cp it into a User.json in your custom stacks folder (stacks/) and find and replace "Patient" with "User". Alternativelly create a temporary User @model then take the auto-generated User.json and move it to your custom stacks folder, then delete the @model.
UserTable.json is not really equivalent the amplify created @model template. It is equivalent to one of the AWS::DynamoDB::Table resources in your cloudformation-template.json (e.g. you would have auto generated DoctorTable and PatientTable dynamodb table resources in your cloudformation-template.json using my example in the gist snippet). It's basically one of these but extracted in its own json.
Interfaces don't work even when they're used as nested properties without an @model
directive. 😢
It will add the property to the return type of queries, but the input types for mutations are missing it. It seems like the feature is there but only half-way. Is this a bug?
So this doesn't work:
type Page @model {
id: ID!
...
blocks: [Block]
}
interface Block {
id: ID!
}
type TextBlock implements Block {
id: ID!
...
}
type ButtonBlock implements Block {
id: ID!
...
}
Same result with unions:
type Page @model {
id: ID!
...
blocks: [Block]
}
type TextBlock {
id: ID!
...
}
type ButtonBlock {
id: ID!
...
}
union Block = TextBlock | ButtonBlock
This is what the generated schema looks like:
type Page {
id: ID!
...
blocks: [Block]
}
input CreatePageInput {
id: ID
# Missing `blocks` field
}
input UpdatePageInput {
id: ID
# Missing `blocks` field
}
hey @ianmartorell did you found how to use the interface without the @model directive?
hey @ianmartorell did you found how to use the interface without the @model directive?
No, what I did was use a discriminator field like this:
enum BlockType {
TEXT
BUTTON
...
}
type Block {
id: ID!
type: BlockType!
... # fields from all block types
}
Most helpful comment
I believe that this is related so I'm asking it here but I can open another question if that would be more appropriate.
I am trying to create a connection between two tables. I would like the child table to contain different types of objects that implement a similar interface. This issue indicates that I can't keep all of the child types in the same table. I tried to keep them in separate tables but then I don't think that
@connection
will support that.I tried something similar to the following:
I get "InvalidDirectiveError: Could not find an object type named Thing". I think that the right way to solve this is to allow multiple types in one table rather than allowing
@connection
to reference multiple tables. Thoughts?