AWS Dynasync
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
- Basic Implementation
- Custom Types
- Mapping the DB to the API
- Interfaces
- Default Table Properties
- Default API Properties
- Security
- License
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
}
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: 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 toDelete
so that they will be deleted if the cloudformation stack is deleted. - 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 thanALLOW
- 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 beInclude
. - 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 beInclude
.
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.