Skip to content
Steven Guggenheimer edited this page Jul 11, 2023 · 4 revisions

AWS Dynasync

Version Build License

AWS Dynasync allows you to create an AWS AppSync GraphQL Api and a set of Amazon DynamoDB tables with a single command. Automate the building and provisioning of a GraphQL API using a single config file with AWS AppSync and Amazon DynamoDb to store the data. The process defines the data tables and GraphQl types to be used. Then using the AWS CDK all of the tables are created, all of the queries and mutations are generated, and all of the data sources connected to have a fully functioning API.

Installation

npm install aws-dynasync

Basic Implementation

   new Dynasync(scope, id);

If you instantiate the class without passing any properties, Dynasync will look for a config file called dynasync.json at the top level of your repository. Alternately you can change the path of the config file:

    new Dynasync(scope, id, {
        configFile: 'path/to/config.json'
    });

You can pass arguments programmatically or through the config file or both. If you use both, Dynasync will merge all tables and types passed programmatically with all tables and types in the config file rather than overwriting anything.

dynasync.json:

    {
        "tables": [
            {
                "tableName": "Dog",
                "partitionKey": "dogId",
                "attributes": {
                    "dogId": "ID!",
                    "breed": "String!",
                    "age": "Int!",
                    "isHousebroken": "Boolean",
                    "name": "String",
                    "description": "String"
                }
            }
        ]
    }

index.ts:

    new Dynasync(scope, id, {
        tables: [
            {
                tableName: "Cat",
                partitionKey: 'catId',
                attributes: {
                    catId: "ID!",
                    breed: "String!",
                    age: "Int!",
                    isHousebroken: "Boolean",
                    name: "String",
                    description: "String"
                }
            }
        ]
    });

The previous snippet would produce DynamoDB tables for both Dog and Cat. Notice that the attributes field values are all GraphQL types. This is how Dynasync maps table attributes to GraphQL schema types.

Custom Types

As previously stated, the attributes field of the SchemaTable object maps DynamoDB table attributes to GraphQL types. The previous examples only show attributes with built in GraphQL scalar types, but you can also use custom types.

Dynasync already creates types and inputs for each DynamoDB table, so you can use any table name as a type in the attributes field of another table:

    {
        "tables": [
            {
                "tableName": "Dog",
                "partitionKey": "dogId",
                "attributes": {
                    "dogId": "ID!",
                    "name": "String",
                }
            },
                        {
                "tableName": "Cat",
                "partitionKey": "catId",
                "attributes": {
                    "catId": "ID!",
                    "name": "String",
                }
            },
            {
                "tableName": "Owner",
                "partitionKey": "ownerId",
                "attributes": {
                    "ownerId": "ID!",
                    "dogs": "[Dog]",
                    "cats": "[Cat]"
                }
            },
        ]
    }

But if you want to declare additional GraphQL types, inputs, interfaces, unions, or enums, you can do so using the types property:

{
    "types": {
        "MyCustomType": {
            "customField1": "String"
        }
    },
    "enums": {
        "MyEnum": [
            "Value1",
            "Value2",
            "Value3"
        ]
    }
}

Mapping the DB to the API

Here is an example of how Dynasync takes a single config file and uses it to map DynamoDB tables to an AppSync API:

Config File

{
    "tables": [
        {
            "tableName": "Dog",
            "partitionKey": "dogId",
            "auto": true,
            "scan": true,
            "attributes": {
                "dogId": "ID!",
                "breed": "String!",
                "age": "Int!",
                "isHousebroken": "Boolean",
                "name": "String",
                "description": "String"
            },
            "globalSecondaryIndexes": [
                "name",
                "age",
                "breed",
                "isHousebroken"
            ]
        },
        {
            "tableName": "Cat",
            "partitionKey": "catId",
            "auto": true,
            "scan": true,
            "attributes": {
                "catId": "ID!",
                "breed": "String!",
                "age": "Int!",
                "isHousebroken": "Boolean",
                "name": "String",
                "description": "String"
            },
            "globalSecondaryIndexes": [
                "name",
                "age",
                "breed",
                "isHousebroken"
            ]
        },
        {
            "tableName": "Event",
            "partitionKey": "eventId",
            "auto": true,
            "attributes": {
                "eventId": "ID!",
                "eventName": "String!",
                "startTime": "AWS_TIMESTAMP",
                "endTime": "AWS_TIMESTAMP"
            },
            "globalSecondaryIndexes": [
                {
                    "partitionKey": "eventName",
                    "sortKey": "startTime"
                }
            ]
        },
        {
            "tableName": "EventSignup",
            "partitionKey": "eventId",
            "sortKey": "dogId",
            "scan": true,
            "attributes": {
                "eventId": "String!",
                "dogId": "String!",
                "category": "String"
            },
            "globalSecondaryIndexes": [
                {
                    "partitionKey": "dogId",
                    "sortKey": "eventId"
                },
                {
                    "partitionKey": "category",
                    "sortKey": "dogId"
                }
            ],
            "localSecondaryIndexes": [ "category" ]
        }
    ],
    "types": {
        "enums": {
            "Terms": [
                "Term1",
                "Term2",
                "Term3"
            ],
            "Definitions": [
                "Def1",
                "Def2",
                "DevOps",
                "Mainframe",
                "Database",
                "Resiliency",
                "Infrastructure",
                "Connect",
                "SAP"
            ]
        }
    }
}

Index.ts

import { App, Stack } from 'aws-cdk-lib';
import { Dynasync } from 'aws-dynasync'; 
import { UserPool } from 'aws-cdk-lib/aws-cognito';

const env = {
  account: "1234567890",
  region: "us-east-1"
}

const app = new App();
const stack = new Stack(app, 'dynasync-stack', {env});


const sync = new Dynasync(stack, 'DynasyncConstruct');
app.synth()

Schema.graphql

    schema {  
        query: Query 
        mutation: Mutation
    }
    
    type Dog {  
        dogId: ID!  
        breed: String!
        age: Int!
        isHousebroken: Boolean
        name: String
        description: String
    }
    
    input DogInput {
        breed: String!
        age: Int!
        isHousebroken: Boolean
        name: String
        description: String
    }
    
    type Query {
        getCatByCatId(catId: ID!): Cat
        getDogByDogId(dogId: ID!): Dog
        getEventByEventId(eventId: ID!): Event
        getEventByEventNameAndStartTime(eventName: String! startTime: AWSTimestamp): Event
        getEventSignupByCategoryAndDogId(category: String dogId: String!): EventSignup
        getEventSignupByEventIdAndCategory(eventId: String! category: String): EventSignup
        getEventSignupByEventIdAndDogId(eventId: String!): EventSignup
        getEventSignupByDogIdAndEventId(dogId: String! eventId: String!): EventSignup
        listCatByAge(age: Int!): [Cat]
        listCatByBreed(breed: String!): [Cat]        
        listCatByIsHousebroken(isHousebroken: Boolean): [Cat]
        listCatByName(name: String): [Cat]
        listDogByAge(age: Int!): [Dog]
        listDogByBreed(breed: String!): [Dog]        
        listDogByIsHousebroken(isHousebroken: Boolean): [Dog]
        listDogByName(name: String): [Dog]
        listEventSignupByEventId(eventId: String!): [EventSignup]
        listEventByEventName(eventName: String!): [Event]
        listEventSignupByCategory(category: String): [EventSignup]
        listEventSignupByDogId(dogId: String!): [EventSignup]
        scanCat: [Cat]
        scanDog: [Dog]
        scanEventSignup: [EventSignup]
    }
        
    type Mutation {
        createCat(input: CatInput): Cat
        createDog(input: DogInput): Dog                
        createEvent(input: EventInput): Event
        deleteCat(catId: ID!): Cat
        deleteDog(dogId: ID!): Dog
        deleteEvent(eventId: ID!): Event
        deleteEventSignup(eventId: String! dogId: String!): EventSignup
        putCat(catId: ID! input: CatInput): Cat
        putDog(dogId: ID! input: DogInput): Dog
        putEvent(eventId: ID! input: EventInput): Event
        putEventSignup(eventId: String! dogId: String! input: EventSignupInput): EventSignup
    }
        
    type Cat {
        catId: ID!
        breed: String!
        age: Int!
        isHousebroken: Boolean
        name: String
        description: String
    }
        
    input CatInput {
        breed: String!
        age: Int!
        isHousebroken: Boolean
        name: String
        description: String
    }
        
    type Event {
        eventId: ID!
        eventName: String!
        startTime: AWSTimestamp
        endTime: AWSTimestamp
    }
    
    input EventInput {
        eventName: String!
        startTime: AWSTimestamp
        endTime: AWSTimestamp
    }
        
    type EventSignup {
        eventId: String!
        dogId: String!
        category: String
    }
        
    input EventSignupInput {
        category: String
    }
        
    enum Terms {
        Term1
        Term2
        Term3
    }
        
    enum Definitions {
        Def1
        Def2
        DevOps
        Mainframe
        Database
        Resiliency
        Infrastructure
        Connect
        SAP
    }

DynamoDB Tables

DynamoDB Tables

Delete Tables With Stack

Unlike most resources, AWS CDK DynamoDB tables are set to be retained by default when their CloudFormation stack is deleted. In most cases this is preferable to ensure that data is not lost by mistake, but it can also cause issues during the development process and in lower environments when stacks are often deleted and run again.

For this reason you can set Dynasync to delete tables along with their stacks by setting deleteTablesWithStack to true:

    new Dynasync(scope, id, {
        deleteTablesWithStack: true
    });

Warning: When this field is set to true, removing the CloudFormation stack could cause the loss of all saved data, so it's not recommended to do this in production environments. Exercise with caution.

Cognito User Pools

If you don't supply a Cognito User Pool when instantiating the Dynasync object, a basic one will be created. But since you'll most likely want to configure the User Pool yourself, it's highly advised to pass your own IUserPool as an argument:

const userPool = new UserPool(stack, "UserPool", {
  userPoolName: 'SyncPool',
  // ...configure user pool here
});

const sync = new Dynasync(stack, 'DynasyncConstruct', {
  userPool
});

Interfaces

DynasyncProps

  • tables (required): SchemaTable[] - An array of SchemaTable objects that will be used first to construct the Amazon DynamoDB tables, then will connect those tables to the Aws Appsync GraphQL API.
  • configFile (default: 'dynasync.json'): string - Custom path to config file
  • userPool (default: if auth exists,undefined; otherwise a basic user pool will be created): IUserPool - The Cognito User Pool that the AppSync API will use for authentication and authorization
  • types (default: undefined): GraphQlTypeList - Custom types in addition to the types and inputs created for each DynamoDB table
  • deleteTablesWithStack (default: false): boolean - If true, sets the UpdateReplacePolicy for all DynamoDB tables to Delete so that they will be deleted if the cloudformation stack is deleted.
  • auth (default: undefined): AuthorizationMode[] - Optional array of AuthorizationModes to use for authentication to the AppSync API. If a value for userPool is supplied, that will be the default authorization mode and these will be the additionalAuthorizationModes. Otherwise the first item in this array will be the default authorization mode.
  • userPoolRegex (default: undefined): string - The value passed to the user pool config's appIdClientRegex field
  • userPoolDeny (default: false): boolean - If true, the Cognito User Pool's default action will be DENY rather than ALLOW
  • apiProps (default: Default API Props) - Override default properties of the CDK Appsync GraphQLAPI construct used to create the API
  • tableProps (default: Default Table Props) - Override default properties of the CDK DynamoDB Table construct used to create your database tables

SchemaTable

  • tableName (required): string - The name of the DynamoDB table to be created
  • partitionKey (required): string - The attribute name of the table's partition key.
  • sortKey (default: undefined): string - The attribute name of the table's sort key. See here for more on DynamoDB partition and sort keys.
  • attributes (required): object - An object containing the attributes that will be stored in the table. The key must be the name of the attribute and the value must be the attribute's data type using GraphQL syntax. See here for more on GraphQL type syntax.
  • globalSecondaryIndexes (default: undefined): string | SchemaGlobal - An array of partition key names or SchemaGlobal objects that define what the table's Global Secondary Indexes (GSI) will be. See here for more on Global Secondary Indexes.
  • localSecondaryIndexes (default: undefined): string | SchemaLocal - An array of sort key names or SchemaLocal objects that define what the table's Local Secondary Indexes (LSI) will be. See here for more on Local Secondary Indexes.
  • scan (default: false): boolean - If true, will add an Appsync API call that performs a scan on the entire table.
  • auto (default: false): boolean - If true, will set table's Appsync API call to generate an auto ID when a new item is created.
  • subscription (default: false): boolean - If true, API will create GraphQL subscriptions for the table.
  • query (default: true): boolean - If true, API will create GraphQL queries for the table.
  • mutation (default: true): boolean - If true, API will create GraphQL mutations for the table.
  • tableProps (default: Default Table Props) - Override default properties of the CDK DynamoDB Table construct used to create your database tables

SchemaGlobal

  • partitionKey (required): string - The attribute name of the secondary index's partition key.
  • sortKey (default: undefined): string - The attribute name of the secondary index's sort key.
  • indexName (default: auto-generated index name): string - The name of the Global Secondary Index
  • list (default: if sort key exists, true; otherwise false): boolean - Boolean value to indicate whether a Global Secondary Index with just a partition key will return a list or not. If a sort key is provided this will always be true because the sort key would be needed to find a unique item, but if just a partition key is provided and the partition key will not return a single, unique item, then this MUST be set to true.
  • include (default: undefined): string - A list of non-key table attribute names that will be included in the index. Providing a value for include will cause the index's Attribute Projection to be Include.
  • capacity (default: undefined): Capacity - Optionally declare the capacity for the index

SchemaLocal

  • sortKey (required): string - The attribute name of the secondary index's sort key.
  • indexName (default: auto-generated index name): string - The name of the Local Secondary Index
  • include (default: undefined): string - A list of non-key table attribute names that will be included in the index. Providing a value for include will cause the index's Attribute Projection to be Include.

GraphQlTypeList

  • types (default: undefined): object - Custom GraphQL types where the key is the type name and the value is an object that maps the type's fields to GraphQL syntaxed types
  • interfaces (default: undefined): object - Custom GraphQL interfaces where the key is the interface name and the value is an object that maps the interface's fields to GraphQL syntaxed types
  • inputs (default: undefined): object - Custom GraphQL interfaces where the key is the input name and the value is an object that maps the input's fields to GraphQL syntaxed types
  • unions (default: undefined): object - Custom GraphQL unions where the key is the union name and the value is an array of which types to use to form the union
  • enums (default: undefined): object - Custom GraphQL enums where the key is the enum name and the value is an array of strings representing each value in the enum

Capacity

  • read (default: undefined): number - Read capacity for a global secondary index
  • write (default: undefined): number - Write capacity for a global secondary index

Default Table Props

Besides tableName, partitionKey, and sortKey which are set at the top level of each SchemaTable object, any properties that can be passed to an AWS CDK DynamoDB L2 Construct can be overridden using the tableProps field of the DynasyncProps object. If these properties aren't overridden, the table defaults are:

    const tableProps = {
        readCapacity: 5,
        writeCapacity: 5,
        replicationTimeout: Duration.minutes(30),
        replicationRegions: undefined,
        // If replicationRegions exist
        billingMode: BillingMode.PAY_PER_REQUEST,
        // If no replicationRegions exist
        billingMode: BillingMode.PROVISIONED,
        pointInTimeRecovery: true,
        tableClass: TableClass.STANDARD,
        encryption: TableEncryption.DEFAULT,
        encryptionKey: undefined,
        timeToLiveAttribute: undefined,
        // If replicationRegions exist
        stream: StreamViewType.NEW_AND_OLD_IMAGES,
        // If no replicationRegions exist
        stream:undefined,
        waitForReplicationToFinish: true,
        contributorInsightsEnabled: false,
        deletionProtection: false,
        kinesisStream: undefined,
        removalPolicy: RemovalPolicy.RETAIN
    }

Default API Props

Besides name, schema, and authorizationConfig which are set using values passed at the top level of the DynasyncProps object, any properties that can be passed to an AWS CDK Appsync GraphQL API L2 Construct can be overridden using the apiProps field of the DynasyncProps object. If these properties aren't overridden, the api defaults are:

    const apiProps = {
        xrayEnabled: true
        logConfig: undefined
        domainName: undefined
    }

Security

See CONTRIBUTING for more information.

License

This project is licensed under the Apache-2.0 License.