Package: pwrdrvr.microapps.cdk
OverviewThe MicroApps project enables rapidly deploying many web apps to AWS on a single shared host name, fronted by a CloudFront Distribution, serving static assets from an S3 Bucket, and routing application requests via API Gateway. MicroApps is delivered as a CDK Construct for deployment, although alternative deployment methods can be used if desired and implemented.MicroApps allows many versions of an application to be deployed either as ephemeral deploys (e.g. for pull request builds) or as semi-permanent deploys. Themicroapps-routerLambda function handled routing requests to apps to the current version targeted for a particular application start request using rules as complex as one is interested in implementing (e.g. A/B testing integration, canary releases, per-user rules for logged in users, per-group, per-deparment, and default rules).Users start applications via a URL such as[/{prefix}]/{appname}/, which hits themicroapps-routerLambda@Edge OriginRequest handler that looks up the version of the application to be run, and either forwards the request to the target Lambda Function URL (--startupType directinvoke mode) or returns a transparentiframe(--startupType iframe) with a link to that version.directmode works with frameworks, like Next.js, that can return pages that have build-time computed relative URLs to static resources and API calls.iframemode works with frameworks that do not write computed relative URLs at build time and/or that do not use URLs that are completely relative to wherever the applications is rooted at runtime; this mode is primarily for quick prototyping as it has other complications (such as indirect access to query strings). The URL seen by the user in the browser (and available for bookmarking) has no version in it, so subsequent launches (e.g. the next day or just in another tab) will lookup the version again. All relative URL API requests (e.g.some/api/path) will go to the corresponding API version that matches the version of the loaded static files, eliminating issues of incompatibility between static files and API deployments.For development / testing purposes only, each version of an applicaton can be accessed directly via a URL of the patterns[/{prefix}]/{appname}?appver={semver}fordirectmode or[/{prefix}]/{appname}/{semver}/foriframemode. These "versioned" URLs are not intended to be advertised to end users as they would cause a user to be stuck on a particular version of the app if the URL was bookmarked. Note that the system does not limit access to particular versions of an application, as of 2023-03-04, but that can be added as a feature.Table of ContentsOverviewWhy MicroAppsRequest Routing for Static Assets / App - DiagramRequest Dispatch Model for Multi-Account DeploymentsVideo Preview of the Deploying CDK ConstructInstallation / CDK ConstructsTutorial - Bootstrapping a DeployLimitations / Future DevelopmentRelated Projects / ComponentsWhy Lambda @ Origin and Not Lambda @ Edge for AppsArchitecure DiagramProject LayoutCreating a MicroApp Using Zip Lambda FunctionsCreating a MicroApp Using Docker Lambda FunctionsNext.js AppsModify package.jsonInstall DependenciesDockerfilenext.config.jsTroubleshootingCloudFront Requests to API Gateway are Rejected with 403 ForbiddenSignatureV4 HeadersWhy MicroAppsMicroApps are like micro services, but for Web UIs. A MicroApp allows a single functional site to be developed by many independent teams within an organization. Teams must coordinate deployments and agree upon one implementation technology and framework when building a monolithic, or even a monorepo, web application.Teams using MicroApps can deploy independently of each other with coordination being required only at points of intentional integration (e.g. adding a feature to pass context from one MicroApp to another or coordination of a major feature release to users) and sharing UI styles, if desired (it is possible to build styles that look the same across many different UI frameworks).MicroApps also allow each team to use a UI framework and backend language that is most appropriate for their solving their business problem. Not every app has to use React or Next.js or even Node on the backend, but instead they can use whatever framework they want and Java, Go, C#, Python, etc. for UI API calls.For internal sites, or logged-in-customer sites, different tools or products can be hosted in entirely independent MicroApps. A menuing system / toolbar application can be created as a MicroApp and that menu app can open the apps in the system within a transparent iframe. For externally facing sites, such as for an e-commerce site, it is possible to have a MicroApp serving/product/..., another serving/search/..., another serving/, etc.Request Routing for Static Assets / App - DiagramRequest Dispatch Model for Multi-Account DeploymentsNote: requests can also be dispatched into the same account, but this model is more likely to be used by organizations with many AWS accounts.Video Preview of the Deploying CDK ConstructInstallation / CDK Constructsnpm i --save-dev @pwrdrvr/microapps-cdkAddMicroAppsconstruct to your stackTheMicroAppsconstruct does a "turn-key" deployment complete with the Release appConstruct HubCDK API docsPython, DotNet, Java, JS/TS installation instructionsTutorial - Bootstrapping a Deploygit clone https://github.com/pwrdrvr/microapps-core.gitNote: the repo is only being for the example CDK Stack, it is not necessary to clone the repo when used in a custom CDK Stackcd microapps-corenpm i -g aws-cdkInstall AWS CDK v2 CLIasp [my-sso-profile-name]Using theawsplugin fromoh-my-zshfor AWS SSOOf course, there are other methods of setting env varsaws sso loginEstablish an AWS SSO sessionexport AWS_REGION=us-east-2Region needs to be set for the Lambda invoke - This can be done other ways in~/.aws/configas well./deploy.shDeploys the CDK StackEssentially runs two commands along with extraction of outputs:npx cdk deploy --context @pwrdrvr/microapps:deployReleaseApp=true microapps-basicnpx pwrdrvr publish --app-name release --new-version ${RELEASE_APP_PACKAGE_VERSION} --deployer-lambda-name ${DEPLOYER_LAMBDA_NAME} --app-lambda-name ${RELEASE_APP_LAMBDA_NAME} --static-assets-path node_modules/@pwrdrvr/microapps-app-release-cdk/lib/static_files/release/${RELEASE_APP_PACKAGE_VERSION}/ --overwrite --no-cacheURL will be printed as last outputLimitations / Future DevelopmentAWS OnlyFor the time being this has only been implemented for AWS technologies and APIsIt is possible that Azure and GCP have sufficient support to enable porting the frameworkCDK would have to be replaced as well (unless it's made available for Azure and GCP in the near future)Release RulesCurrently only a Default rule is supportedNeed to evaluate if a generic implementation can be made, possibly allowing plugins or webhooks to support arbitrary rulesIf not possible to make it perfectly generic, consider providing a more complete reference implementation of examplesRelated Projects / ComponentsRelease AppThe Release app is an initial, rudimentary, release control console for setting the default version of an applicationBuilt with Next.jspwrdrvr/microapps-app-releaseNext.js Demo AppThe Next.js Tutorial application deployed as a MicroApppwrdrvr/serverless-nextjs-demoWhy Lambda @ Origin and Not Lambda @ Edge for AppsCalling resources (DBs and other services) and waiting for a long synchronous response is an anti-pattern in Lambda as the Lambda function will be billed for the time spent waiting for the response. This is especially true for Lambda@Edge as the cost is 3x higher than Lambda at the origin.With Lambda@Edge (even with Origin Requests) the cost is 3x higher per GB-second and the time spent waiting for a 1 ms service response from an origin that is 250 ms away is 750x higher (250 ms / 1ms * 3x higher cost) than making that same request within the region where the resource resides.Lambda@Edge isat least3x more expensive than Lambda at the origin:In US East 1, the price per GB-Second is $0.00005001 for Lambda@EdgeSource:https://aws.amazon.com/lambda/pricing/(bottom of page)Updated: 2023-03-04In US East 1, the price per GB-Second is $0.0000166667 for Lambda at the origin on x86Source:https://aws.amazon.com/lambda/pricing/Updated: 2023-03-04RatioLambda@Edge / Lambda@Origin = $0.00005001 / $0.0000166667 = 3.0006xAny DB or services calls from Lambda@Edge back to the origin will pay that 3x higher per GB-Second cost for any time spent waiting to send the request and get a response. Example:Lambda@Edge0.250s Round Trip Time (RTT) for EU-zone edge request to hit US-East 1 Origin0.200s DB lookup time0.050s CPU usage to process the DB response0.500s total billed time @ $0.00005001 @ 128 MB$0.000003125625 total chargeLambda at OriginRTT does not apply (it's effectively 1-2 ms to hit a DB in the same region)0.200s DB lookup time0.050s CPU usage to process the DB response0.250s total billed time @ $0.0000166667 @ 128 MBHalf the billed time of running on Lambda@Edge1/6th the cost of running on Lambda@Edge:$0.000000520834375 total charge (assuming no CPU time to process the response)$0.000003125625 / $0.000000520834375 = 6x more expensive in Lambda@EdgeArchitecure DiagramProject Layoutpackages/cdkExample CDK StackDeploys MicroApps CDK stack for the GitHub WorkflowsCan be used as an example of how to use the MicroApps CDK Constructpackages/demo-appExample app with static resources and a Lambda functionDoes not use any Web UI framework at allpackages/microapps-cdkMicroApps"Turn key" CDK Construct that creates all assets needed for a working MicroApps deploymentMicroAppsAPIGwyCreate APIGateway HTTP APICreates domain names to point to the edge (Cloudfront) and origin (API Gateway)MicroAppsCFCreates Cloudfront distributionMicroAppsS3Creates S3 bucketsMicroAppsSvcsCreate DynamoDB tableCreate Deployer Lambda functionCreate Router Lambda functionpackages/microapps-datalibInstalled fromnpm:npm i -g @pwrdrvr/microapps-datalibAPIs for access to the DynamoDB Table used by thepwrdrvrCLI,microapps-deployer, and@pwrdrvr/microapps-app-release-cdkpackages/microapps-deployerLambda service invoked by thepwrdrvrCLI to record new app/version in the DynamoDB table, create API Gateway integrations, copy S3 assets from staging to prod bucket, etc.Returns a temporary S3 token with restricted access to the staging S3 bucket for upload of the static files for one app/semverpackages/pwrdrvrInstalled fromnpm:npm i -g pwrdrvrNode executable that updates versions in config files, deploys static assets to the S3 staging bucket, optionally compiles and deploys a new Lambda function version, and invokesmicroapps-deployerAWS IAM permissions required:lambda:InvokeFunctionpackages/microapps-routerLambda function that determines which version of an app to point a user to on a particular invocationCreating a MicroApp Using Zip Lambda Functions[TBC]Creating a MicroApp Using Docker Lambda FunctionsDocker Lambdas are great for large applications.  These used to be slower to cold start but as of early 2023 that appears to no longer be an issue.Next.js AppsCreate a Next.js app then follow the steps in this section to set it up for publishing to AWS Lambda @ Origin as a MicroApp. To publish new versions of the app usenpx pwrdrvr --new-version x.y.zwhen logged in to the target AWS account.Modify package.jsonReplace the version with0.0.0so it can be modified by thepwrdrvrCLI.Install Dependenciesnpm i --save-dev pwrdrvrDockerfileFIXME: Out of date 2023-03-04Add this file to the root of the app.FROMnode:15-slimasbaseWORKDIR/app# Download the sharp libs once to save time# Do this before copying anything else inRUNmkdir-pimage-lambda-npms&&\cdimage-lambda-npms&&npmisharp&&\rm-rfnode_modules/sharp/vendor/*/include/# Copy in the build output from `npx serverless`COPY.serverless_nextjs.COPYconfig.json.# Move the sharp libs into placeRUNrm-rfimage-lambda/node_modules/&&\mvimage-lambda-npms/node_modulesimage-labmda/&&\rm-rfimage-lambda-npmsFROMpublic.ecr.aws/lambda/nodejs:14ASfinal# Copy in the munged codeCOPY--from=base/app.CMD["./index.handler"]next.config.jsFIXME: Out of date 2023-03-04Add this file to the root of the app.Replaceappnamewith your URL path-compatible application name.constappRoot='/appname/0.0.0';// eslint-disable-next-line no-undefmodule.exports={target:'serverless',webpack:(config,_options)=>{returnconfig;},basePath:appRoot,publicRuntimeConfig:{// Will be available on both server and clientstaticFolder:appRoot,},};TroubleshootingCloudFront Requests to API Gateway are Rejected with 403 ForbiddenRequests to the API Gateway origin can be rejected with a 403 Forbidden error if the signed request headers are not sent to the origin by CloudFront.The error in the API Gateway CloudWatch logs will show up as:"authorizerError": "The request for the IAM Authorizer doesn't match the format that API Gateway expects."This can be simulated by simply runningcurl [api-gateway-url], with no headers.To confirm that API Gateway is allowing signed requests when the IAM Authorizer is configured, establish credentials as a user that is allowed to execute the API Gateay, installawscurlwithpip3 install awscurl, then then useawscurl --service execute-api --region [api-gateway-region] [api-gateway-url].Signature headers will not be sent from CloudFront to API Gateway unless theOriginRequestPolicyis set to specifically include those headers on requests to the origin, or theheadersBehavioris set tocfront.OriginRequestHeaderBehavior.all().Similarly, ifpresignis used, theOriginRequestPolicymust be set tocfront.OriginRequestQueryStringBehavior.all()or to specifically forward the query string parameters used by the presigned URL.SignatureV4 Headersauthorizationx-amz-datex-amz-security-tokenx-amz-content-sha256