Re-implementation of the AWS Amplify CLI with pure CDK.
- The AWS Amplify GraphQL Specification transformation to DynamoDB Table Configuration / AppSync GraphQL Resolvers is fantastic.
- Amplify's Auth/UI library is fantastic.
- Amplify works great for prototyping applications.
However:
- The developer experience of deploying, managing and configuring moderately-complex AWS Applications via AWS Amplify can be sub-par.
- Using AWS Amplify will hide key infrastructure resources (CloudFront, CodeBuild) from you. When there are issues, you often have no way to debug/diagnose/fix these issues without going through support, which can be frustrating.
- We found deployment of Amplify CLI-managed infrastructure to be very slow: we'd often wait 10-20 minutes for a deployment to complete, even when making very small changes.
- AWS Amplify undergoes a high pace of development and is often unstable. Whilst it's a fantastic technology for rapid prototyping, we found building stable production-ready applications on top of it frustrating. We ran into bugs regularly.
- Because of the hidden complexity within AWS Amplify, when you run into an AWS Amplify bug it is very easy to become blocked. Its internals are not well documented, and we found ourselves spending a lot of time digging through the code to find workarounds.
- We found integrating our Amplify-managed components with non-Amplify components (an AWS Fargate Node.JS server) to be complex.
In contrast:
- The Developer experience with AWS CDK of deploying, managing and configuring AWS Applications is fantastic. We've never run into a library bug, and the documentation is fantastic.
- The AWS CDK CLI supports hotswap deployments, enabling very fast (<1 minute) deployment of components like AWS Lambda.
Any code that runs is code that can fail. After a frustrating week fighting a library bug we decided that the cost of re-implementing the Amplify CLI components with pure CDK was less than the cost of continuing to battle with the AWS Amplify CLI.
This project is an extraction of the work we did to eject https://reflow.io from the AWS Amplify CLI. The core design motivation is reducing the code-paths that run to deploy a given application to a minimum: just the AWS CDK CLI.
This project is for teams who:
- Want to make their Amplify Application ready for Production: stabilizing it with patterns for Error Monitoring, Backup/Restore, and multi-environment deployment.
- Are considering leaving AWS Amplify, but want to do this gradually, avoid the need to rewrite their application code.
- Are looking to use AWS Amplify's GraphQL Schema to AppSync Resolver / DynamoDB component standalone in another application.
- Zero-Install development / deployment process, via Yarn v3 / PnP
- AWS Amplify Schema compatibility, via direct invocation of Amplify's Schema Transformer toolchain
- ESBuild-compiled Node.JS Lambdas
- React Hooks for querying/live-update/caching of authorized DynamoDB data via Apollo
- Continuous deployment via CDK Pipelines
On top of this, a few utility features are provided:
- Backup/Restore capability for all components: including Cognito Userpools
- Pre-configured AWS Lambda functions for highly-parallel data migration between Environments
- Slack ChatOps integration for release notifications and Lambda Error Monitoring
- Iterative DynamoDB table update script to handle multiple GSI (Global Secondary Index) updates in a single deployment
This architecture has been battle-tested and is in production use at several companies.
- To use this, your team will need to take ownership of a significant amount of complexity. This complexity may mean learning AWS CDK, and using it to manage your infrastructure, instead of learning how Amplify configures infrastructure. For small applications, this will add complexity, not remove it.
- We haven't implemented any of the following amplify directives. Additional work would be needed before automatically
creating these resources in a Reamplify application from parsed graphql directives.
@searchable
@predictions
@http
- This repository doesn't work with Amplify DataStore. We do have a fork of this library which re-enables the DataStore APIs (https://reflow.io uses DataStore), but because enabling DataStore modifies both GraphQL DynamoDB attributes and adds a lot of complexity we prefer to keep the library as simple as possible.
-
This repository contains everything needed to deploy a Reamplify app. All AWS Components are 100% configurable. We recommend that you maintain a private fork of this repository to tweak to your needs.
-
The deployment configuration does not use any AWS Amplify resources, instead:
- Amplify Hosting is replaced by a S3 Static Site w/ Cloudfront
- Amplify Automated Deployment is replaced by CodePipeline / CodeBuild
- DNS is provided by Route53
-
AWS Amplify's transformer libraries are used in the
packages/schema
folder to compile AppSync resolvers. This transformation process happens once perschema.graphql
change, and the resolvers are checked into the codebase. This ensures that the application can be stably deployed by CI servers. -
Patches to amplify's generated VTL can be made via several patterns, depending on the scope of the patch:
- Introducing an
overrides
folder, and hooking into it intransformSchema.ts
- By overriding the transformer in
transformers.ts
. - By adding find/replace rules before these are written to disk in
transformSchema.ts
- Introducing an
The repository is configured as a yarn workspaces monorepo.
In reflow, additional subfolders exist for each independently developed artifacts, for instance our browser Session-recording application. By building this way we can share components (such as GraphQL API snippets) between all of these projects with type-safety via typescript.
In reamplify, we've provided the minimal set of components to build a basic CRUD web-application, and a homepage.
packages/schema - Home for the GraphQL Schema, Resolvers, and additional common artifacts
packages/webapp - Home for the frontend web application
packages/homepage - Home for the reamplify.io homepage
packages/deploy - Home for the CDK deployment
- Safe, stable, reproducible applications. Powered By:
- Corepack: a binary shipped with Node.JS >= 16.10.
- This exposes binary proxies to solidify the node.js engine version
- Yarn v3 w/ PnP
- This removes the need for
npm install
after cloning the repository.
- This removes the need for
- Pre-generated appsync resolvers
- This allows for a reproducible Amplify Resolver deployment. Any changes that modify Resolvers can have their VTL code-reviewed directly in a PR / Diff.
- Corepack: a binary shipped with Node.JS >= 16.10.
- All components automatically deploy from a repository, but can be manually deployed when needed. Powered By:
- Two CDK application targets: one for CDK Pipelines, one for the CDK CLI. These reference a single application file which configures the application stacks in the same way.
- CodeBuild Buildspec files checked into the repository
- Fast, pleasant development experience. Powered By:
- Create-React-App for frontend, pointing at an arbitrary backend
- Fast (~20 seconds) hotswap deployment of AWS Lambda Functions via CDK Hotswap
- Typescript-powered CDK allows for rapid reconfiguration of application infrastructure. Specific design decisions (no nested stacks) made to allow for significant infrastructure changes without manual deployment effort.
- Production-Ready application architecture. Powered By:
- All Backend components being serverless: your development environments can be exact duplicates of your production environments without incurring significant cost.
- All Backend Lambdas get automatic monitoring via CloudWatch / Slack error alerting
- Cognito Trigger Lambdas allows for cognito
sub
changes for backup/restore of a Cognito Userpool. - Serverless DynamoDB w/ Copy-Environment Lambdas available for rapid disaster recovery and environment migration scenarios
This codebase assumes you have corepack enabled (for yarn), or are operating on Node >= v16.10.
- Clone this repository:
git clone git@github.com:ThomasRooney/reamplify.git
- Install dependencies (optional with yarn zero-install):
yarn install
- Navigate into webapp folder:
cd packages/webapp
- Run the webapp:
yarn start
Subsequent code changes will be hot-reloaded.
This codebase assumes you have corepack enabled (for yarn), or are operating on Node >= v16.10.
This codebase assumes you have an AWS Account configured, Node.js >= v16.10 installed, and corepack enabled (for yarn)
- Clone this repository:
git clone git@github.com:ThomasRooney/reamplify.git
- Install dependencies (optional with yarn zero-install):
yarn install
- Navigate into deploy folder:
cd packages/deploy
- Make any changes you want.
- Deploy the changes.
- If your changes are just lambda function changes, just deploy lambda with
yarn run apply:dev:functions
. - If you made changes to more than just lambda function content, you can deploy everything with
yarn run apply:dev:all
- If your changes are just lambda function changes, just deploy lambda with
- Repeat [4,5] until your application works. Approximate time for a set of lambda changes to be live is ~30s.
On top of the frontend requirements, to deploy a backend assumes you have an available AWS Accounts configured:
This codebase assumes you already have DNS configured for a domain of your choice. We will create an NS
record to route
a subdomain to the created example application. In this sequence we assume you own myapp.myurl.com
, but given you do not please
replace that text with something appropriate that you own.
- Fork this repository.
- Navigate to packages/cdk/bin. Adjust
environments.ts
dev
to match your environment:- Open the AWS Console in the target account/region where you want to deploy your application
- Create a hosted zone named
myapp.myurl.com
in your AWS account - Create an NS record from
myurl.com
tomyapp.myurl.com
- Validate the DNS record routes via
dig
. - Update the AWS Account and Region under the
env
variables to yours. - Update the
HostedZoneId
inenvironments.ts
with that hosted zone's ID
- Open
packages/deploy/package.json
. Update thecdk:bootstrap
commands to match the accounts/regions where you want to deploy. - Search for all hardcoded account-ids, validate they are no longer referenced. If referenced, replace with your account IDs
- 515651378965 (Root Organization AWS Account): this is assumed to be the default AWS account configured.
- 805615297525 (Development AWS Account): this is assumed to be the deployment account, and initially accessed via an AWS profile named 805615297525
- Note: if you want to just use a single-account setup, you can use the same account id for both and remove references to the development account.
- Execute
yarn run cdk:bootstrap
. Executeyarn run cdk:bootstrap:dev
- Execute
yarn run cdk:deploy:manual:dev
. Wait for deployment to complete. This will create a new environment from scratch. - Execute
yarn run sync-environments
- Open
packages/webapp/
. Executeyarn run build:dev
- Execute
yarn run frontend:deploy:manual:dev
. This will push the build live tomyapp.myurl.com
. - Validate
myapp.myurl.com
works.
Once you've completed this process, setup continuous deployment on your repository.
- Open the AWS console at the target environment
- Navigate to CodePipeline
- Navigate to Settings => Connections.
- If a connection already exists, take note of the connectionArn. Otherwise, follow https://docs.aws.amazon.com/codepipeline/latest/userguide/connections-github.html to configure a new connection for the account.
- Add
deploy
property to theenvironments/backend
with properties:owner
='MyGithubUsername'
(this is the GitHub repository owner)connectionArn
= the connectionArn from step [4]repo
='reamplify'
(the GitHub repository name you forked this into)branch
= whichever branch you want to trigger the deploy on. Whenever this branch is pushed to the deploy will run. E.g.dev.branch = 'master'
requiresApproval
(set to true if you want a manual approval stage before deployment, false for no approval)approvalEmail: [...emails]
(add any email addresses that should get a notification email when a deploy comes in for approval).
- Create a
cdk/bin/<env>/ci.ts
file that creates a single cloudformation stack undernew CDKPipelineStack
with the backend and userpool configuration that should be deployed. - Create a
synth:<env>
script target incdk/package.json
to synthesise the cloudformation templates for that environment. E.g.npm run build && cdk synth --app "ts-node --prefer-ts-exts bin/thomas/ci.ts"
- Create a
ci:deploy:<env>
script target incdk/package.json
to prepare to actually deploy the CI configuration. E.g."ci:deploy:thomas": "npm run build && cdk deploy --all --app \"ts-node --prefer-ts-exts bin/thomas/ci.ts\""
- Commit/Push to your target branch before proceeding with everything done thus far. The pipeline will self mutate, so it's important that in its first run it will use the latest configuration.
- Execute
ci:deploy:<env>
to deploy the pipeline. - After the deployment is done, watch one full deployment of each of the created pipelines and fix any issues. The
first pipeline will be the pipeline created in [10]. This will self-update itself based on the CDK synth. Subsequent
updates will automatically deploy (including, optionally, the frontend pipeline). If
live: true
is set, a second pipeline will be created to automatically deploy the frontend on pushes to the triggering branch as well. Both of these pipelines adhere to therequiresApproval
configuration; i.e. there will be 2 approval stages.
- Disable dynamodb streams via setting
includeLambdaStreams: false
. Deploy. This will create theCopyTable
lambdas. - Ensure that databases exist with a common suffix and appropriate data. I.e.
- If just moving environments, take note of the table suffix of the source / destination tables
- If restoring from a deleted environment, recreate all tables from DynamoDB backups with a common suffix (e.g. User-05052022-backup)
- If restoring to a specific time from an active environment, perform the dynamodb point-in-time restoration procedure of the active tables to a common suffixx (e.g. User-05052022-backup)
- Execute
copyAllTables
with the source database suffix, destination database suffix,inline: false
, andtotalSegments: 25
.totalSegments
represents the maximum degree of parallelism when copying the database across from the source suffix (it will be automatically lowered if the database is small, via theestimatedTotalRecords
property in dynamodb). Higher is faster, too high and the database might start throttling lambdas. - Watch cloudwatch insights log group
copyTable
for console output / errors. In particular, iffailed to invoke
is found, the job may need to be restarted. The job can be restarted from the last successful request, using thelastEvaluatedKey
property. - (Important) Validate that the number of items in the destination table is the same as the source table. If it is less, repeat the process, lowering
totalSegments
. We have observed, at a high parallelism-to-item ratio, not all data points being scanned. - If a segment cannot complete within 15 minutes (e.g. database size too large), its job may need to be continued manually. Lookup
nextEvent
in cloudwatch insights and manually invokecopyTable
lambda with thesenextEvent
objects.
If the database size is small, this process can be run entirely locally on a developer's machine via mutating the scripts/copy-table.ts
script to point at two environments and setting inline: true
.
- Create a new Cognito user pool.
- Create a Batch Import of users from the
Users
table from your backup. As long asemail
is consistent the user's data will be automatically moved to the newsub
upon their next login. - Deploy the application with the
migratedArn
property set to the new userpool's ARN - Complete the batch import.
On a high level, the migration process from AWS Amplify is:
- Create a copy of all AWS Amplify managed resources with CDK resources.
- For each Amplify-managed Lambda, create an appropriate CDK construct to create it. Configure
lambdas/index.ts
to compile it with esbuild. Symbolically link the old one whilst developing so that CDK work can be merged in parallel with lambda work. - For all other Amplify-managed resources, create appropriate CDK constructs that recreate them.
- (Optional: CI) Configure CodeStar to access your repository. Take note of the Codestar ARN and import it into
environments.ts
- (Optional: Slack) Configure AWS Chatops to access a Slack channel
- Reference the existing userpool with the
migrate
prop. We prefer not to recreate it as otherwise all users will need to reset passwords. Alternatively, do a batch migration of users: whilst thesub
will change thePreConfirmation
lambda will migrateowner
references.
- For each Amplify-managed Lambda, create an appropriate CDK construct to create it. Configure
- Deploy a new environment alongside the old one
- Migrate data via the copy-table lambdas
- Target the new environment to the old userpool.
- Validate that the application is working correctly.
- Change over DNS to point at the new environment.
- Repeat [1,2,3,4] for all other environments.
- Cleanup old resources once they are no longer used.
In our experience, this process takes 1-2 weeks of work for a mid-large AWS amplify project, by adapting reamplify resources into an existing repository. This work can be done in parallel with normal development cycles if done with care.
To enable the Yarn v3 Zero Install configuration, navigate to the root .gitignore
and follow instructions to check in the .yarn/cache
directory.
This is an optional step, but recommended. We didn't want to bloat repository size for this base-repository, but for any professional-grade application our opinion is that the advantages far-outweigh the disadvantages.
- IDE performance tends to be faster using PnP instead of a
node_modules
directory - Deployment is far faster when using Zero-Install, as there's no need to fetch dependencies
- Stability is higher: there's less moving parts that can break
- The size of the repository will grow over time as dependencies are upgraded. This increases initial checkout time
- Many npm packages do not support PnP, and require manual fine-tuning in .yarnrc.yml to bring them in. To support PnP each package needs to explicitly declare their dependencies, and many packages instead just assume that certain other packages are installed.
End-to-end tests are configured via reflow for all releases. Reflow is a low-code AI-augmented browser record/replay tool.
To get started testing, create an account at https://reflow.io
, or execute npm install -g reflowio; reflowio dashboard
to
record/replay test sequences on your locally running software. Reflow will replay these sequences, automatically repair element selectors,
and provide visual feedback / auto-healing workflows.
If you are interested in commercial engineering support, please contact us. We can help with:
- Migration of existing Amplify Apps into Reamplify
- Training
- DevOps as a Service: we'll customise Reamplify and help manage your infrastructure with CDK for all your product needs
- We have built and maintain addons to this project which are not in this repository because they are only applicable to certain products; and generally have to be tailored to a specific product.
- If you would like these capabilities integrated into your project, please contact us: we'll be able to do this for you
far faster than your team implementing it.
- IAM-managed multi-tenancy via AWS Attributes for Access Control; including Lambdas / UIs / APIs / S3 File Upload/Retrieve.
- Team Management / Multi-User Workspaces via Cognito Custom Attributes; including Lambdas / UIs / IAM rules / DynamoDB GSIs
- Licensable API Gateway access into your application
- API Key management / authorization via Web UIs
- AWS Marketplace Integrations
- Usage-based licensing / Subscription Payment models; including Scheduled Lambdas / UIs / Usage Monitoring
- Role-based permission modeling