Amplify-cli: AWS Amplify and GraphQL Interfaces

Created on 16 Sep 2018  路  6Comments  路  Source: aws-amplify/amplify-cli

* Which Category is your question related to? *
Usage for AWS AppSync and AWS Amplify

* What AWS Services are you utilizing? *
AppSync, Dynamodb

* Provide additional details e.g. code snippets *
Please correct me if there's a better place to put this. How would you deal with interfaces and using them for connections in a data model using the AWS Amplify Model Transforms?

For example, I'm not sure how I would go about doing something like this. It seems like my choices are to put @model on the types but then I get separate Dynamo tables and queries on the Query once amplify update api is run.

interface Event @model {
  id: ID
  created: AWSTimestamp
}

type VisitEvent implements Event {
  id: ID
  created: AWSTimestamp
  time: AWSTimestamp 
  somepayload: [SomePayload]
}

type PurchaseEvent implements Event {
  id: ID
  created: AWSTimestamp
  time: AWSTimestamp 
  items: [Item] # some payload
}

type User @model {
  events: [Event] @connection
}
feature-request graphql-transformer pending-review

Most helpful comment

@mikeparisstuff - to me a @model on an interface means that that all models implementing that interface will share the same datasource table. ConnectionStack will also refer to that common data source on queries on those models. Top-level list queries on these models I imagine will also have to filter on the __typename and add a sort index on __typename to do that as you suggested previously.

I already implemented most of this for my own use through a script that I call before doing amplify push that modifies your auto-generated json. To do that I had to do the following: 1) Create my own "Event.json" and "EventTable.json" custom resources in my stacks/ dir that creates the EventIAMRole, EventDataSource, and EventTable. 2) Call the script before doing an amplify push that does the following:

  1. Change the resolvers in VisitEvent.json and PurchaseEvent.json (from the example above) that are auto-created in the build dir to connect to the EventDataSource instead of the VisitEventDataSource, etc..

  2. Change the ConnectionStack.json to connect to the EventDataSource instead of VisitEventDataSource or PurchaseDatasource.

  3. Modify the "DependsOn" settings in the cloudformation-template.json so that the EventTable and Event Data Source resources are created before the VisitEvent.json and PurchaseEvent.json are run.

I did not have to create sort keys on __typename for my use case because I don't need top-level list support at this moment. But I imagine that would have to be done too and then the list* resolvers would have to have "__typename" "eq" "VisitEvent" type filter on the dynamodb queries.

All 6 comments

This is currently not supported but I am open to a discussion about what @model on an interface means. In terms of implementation, the work is to implement the interface method in the model transformer (just like how we implement object here https://github.com/aws-amplify/amplify-cli/blob/adf7ab7de01786535e734c3916e4d149ff1b2bf9/packages/graphql-dynamodb-transformer/src/DynamoDBModelTransformer.ts#L95).

@mikeparisstuff - to me a @model on an interface means that that all models implementing that interface will share the same datasource table. ConnectionStack will also refer to that common data source on queries on those models. Top-level list queries on these models I imagine will also have to filter on the __typename and add a sort index on __typename to do that as you suggested previously.

I already implemented most of this for my own use through a script that I call before doing amplify push that modifies your auto-generated json. To do that I had to do the following: 1) Create my own "Event.json" and "EventTable.json" custom resources in my stacks/ dir that creates the EventIAMRole, EventDataSource, and EventTable. 2) Call the script before doing an amplify push that does the following:

  1. Change the resolvers in VisitEvent.json and PurchaseEvent.json (from the example above) that are auto-created in the build dir to connect to the EventDataSource instead of the VisitEventDataSource, etc..

  2. Change the ConnectionStack.json to connect to the EventDataSource instead of VisitEventDataSource or PurchaseDatasource.

  3. Modify the "DependsOn" settings in the cloudformation-template.json so that the EventTable and Event Data Source resources are created before the VisitEvent.json and PurchaseEvent.json are run.

I did not have to create sort keys on __typename for my use case because I don't need top-level list support at this moment. But I imagine that would have to be done too and then the list* resolvers would have to have "__typename" "eq" "VisitEvent" type filter on the dynamodb queries.

@hisham, @mikeparisstuff, @michael-golfi: Based on this discussion and #202, I am wondering what would @connection behavior will look like.

Based on https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html what are you thoughts about this behavior:

interface Entity @table(name: "test-table") {
    # gets prefixed with TypeName-
    # used both for pk and sk
    id: ID!
}

type Team implements Entity {
    id: ID!
    name: String!
    color: String!
    sport: Sport 
    # will duplicate partial information about sport next to the team
    #  pk      sk 
    # Team-0 Team-0
    # Team-0 Sport-0
    @connection(fields: ["name"])
}

type Sport implements Entity {
    id: ID!
    name: String!
    locality: String!,
    teams: [Team!]!
    # will duplicate partial information about team next to the sport
    #    pk      sk
    # Sport-0 Sport-0
    # Sport-0 Team-0
    # Sport-0 Team-1
    # Sport-0 Team-2
    @connection(fields: ["name"])
}

### results in
type PartialTeamSport {
    name: String!
}

type PartialSportTeam {
    name: String!
}

type Team implements Entity {
    name: String!
    sport: PartialTeamSport
}

type Sport implements Entity {
    name: String!
    teams: [PartialSportTeam!]!
}

type Query {
    # makes Query on table with pk: SPORT-ID
    getSport(id: ID!): Sport
    # makes Query on reverse GSI with pk: TEAM-ID
    getSportForTeamID(id: ID!): Sport

    # makes Query on table with pk: TEAM-ID
    getTeam(id: ID!): Team
    # makes Query on reverse GSI with pk: SPORT-ID
    getTeamsForSportID(id: ID!): [Team!]!
}
  1. id specified on interface marked with @table results in partion_key combining __typename and identifier: Typaname-id. All types implementing this interface will use the same DynamoDB table.
  2. @connection will result in duplicating entries next to entry whose type declares @connection. For connection declared on Team, a sport entry will be duplicated with partition key of team-entity and sort key of sport-entity. to-one and to-many connections will be represented in the same way. one-to-many will result in m + m * n + 2n entries, many-to-many will result in (m + m * n) + (n + n * m) entries, one-to-one with 2 * (m + n).
  3. Based on the access pattern users can specify the fields that will be projected into duplicates with @connection(fields: ["name"]) to limit the space footprint. Not specifying fields will produce duplicates with all original fields. Thus @connection(fields: ["name"]) will result in a following type definition:
type PartialSportTeam {
    name: String
}

type Sport implements Entity {
    name: String
    teams: [PartialSportTeam!]!
}
  1. sort key has the same structure as partition key. Normal entries will have same partition key and same sort key. Relation duplicates will have the sort key matching the entity on the other side of relation (like say we have a sport with 3 teams):
#   pk     sk
# Sport-0 Sport-0
# Sport-0 Team-0
# Sport-0 Team-1
# Sport-0 Team-2
  1. Reverse GCI will be created with partion_key being original table sort_key and sort_key being original table's partion_key
  2. two query resolvers associated @connection.
    type Query { # makes Query on table with pk: SPORT-ID getSport(id: ID!): Sport # makes Query on reverse GSI with pk: TEAM-ID getSportForTeamID(id: ID!): Sport }
    getEntity(id: ID!): EntityType will query with partion key and will return the entity and relation entities in a single query. getEntityByRelatedID(id: ID!): Entity will query on GCI with relation entity id and sort key starting with entity __typename.

Does this make sense?

Closing in favor of #202.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kstro21 picture kstro21  路  3Comments

nicksmithr picture nicksmithr  路  3Comments

jexh picture jexh  路  3Comments

nason picture nason  路  3Comments

kangks picture kangks  路  3Comments