Evolving GraphQL schemas
GraphQL schemas change over the lifecycle of a project. Sometimes these changes include breaking API changes. One such change is renaming a model in the schema, which Amplify offers a way to do while retaining the underlying records for that model.
Renaming models while retaining data
Amplify supports renaming models in a GraphQL schema by using the @mapsTo
directive.
Normally when renaming a model, Amplify will remove the underlying table for the model and create a new table with the new name. Once a table contains production data that cannot be deleted, @mapsTo
can be used to specify the original name. Amplify will use the original name to ensure the underlying DynamoDB tables and other resources point to the existing data.
Other GraphQL API references to the model will use the new name.
For example, a schema such as:
type Todo @model { id: ID! title: String!}
becomes:
type Task @model @mapsTo(name: "Todo") { id: ID! title: String!}
Amplify will update all of the GraphQL operations and types to use the name Task, but the Task model will point to the table that Todo was originally using.
When renaming a model that has relationships with other models, Amplify will automatically map auto-generated foreign key fields to their original name. For example, given:
type Post @model { id: ID! title: String! comments: [Comment] @hasMany}
type Comment @model { id: ID! message: String! # postCommentsId: String is an autogenerated field containing the foreign key}
Amplify will automatically add a field named postCommentsId
to the Comment model that contains the foreign key of the Post. If the Post type is renamed to Article:
type Article @model @mapsTo(name: "Post") { id: ID! title: String! comments: [Comment] @hasMany}
type Comment @model { id: ID! message: String! # articleCommentsId: String is the new autogenerated field containing the foreign key}
The underlying table still contains records with postCommentsId
as the foreign key field in the Comment table. In the new schema the foreign key field is now articleCommentsId
.
Amplify is aware of this and will automatically map incoming requests with articleCommentsId
to postCommentsId
and do the reverse mapping for results.
Limitations
Constraint on relationship field names with @mapsTo
In the above example if you renamed Comment to Reaction:
type Post @model { id: ID! title: String! comments: [Reaction] @hasMany # this field cannot be renamed and still access existing relationship data}
type Reaction @model @mapsTo(name: "Comment") { id: ID! message: String! # autogenerated field postCommentsId: String contains the foreign key}
The @hasMany
field comments
cannot be renamed to reactions
. This is because the foreign key field in Reaction uses the parent field name as part of the name. Amplify cannot determine the original name if this is changed.
If a model is renamed multiple times, the value specified in @mapsTo
must be the original name, not the previous name.
Constraints to prevent naming conflicts
A model in the schema cannot have the same name as the name another type maps to. For example, the following schema is invalid:
type Article @model @mapsTo(name: "Post") { id: ID!}
type Post @model { id: ID!}
This schema would create a conflict on the Post table.
Furthermore, even if the Post model is mapped to a different name, it is still not allowed. While this scenario technically does not pose a conflict, it is disallowed to prevent confusion.
If you are accessing the table of a renamed model directly (ie. without going through AppSync), your access patterns will need to be aware that foreign key fields of records in the database are not renamed. See "How it works" below.
How it works
@mapsTo
does not modify any existing tables or records. Instead, it points AppSync resolvers for the new name to the existing DynamoDB table for the original name.
To handle renamed autogenerated foreign key fields when using relational directives, Amplify adds additional AppSync pipeline resolvers before and after fetching data from the database. The resolvers before the fetch map any occurrence of the renamed foreign keys in the request to the original name. Then the resolvers after the fetch map any occurrence of the original name to the current name before returning the result.