This will be a series of blog posts where we will build up the perfect infrastructure setup for the majority of usecase, aka "The Stack". We'll be building everything on top of AWS.
Before diving in, let's first establish some goals:
Obviously, this is my personal opinion on it, but I'll be sharing the thinking behind each of the choices as we go along.
Some technology choices upfront:
Here's an overview before we get into the details:
All of this will be covered in parts:
Reorganizing your AWS Account structure is a pain, so let's see if we can get this right from the beginning. There are a few things that direct our choices here:
Let's first sketch out the Account Governance structure, before diving into the view of each individual AWS account:
graph TD subgraph ControlTower[AWS: Control Tower] AuditLog[Audit Log] GuardRails[Guard Rails] end ControlTower-->AWSProdMultiTenantAccount ControlTower-->AWSProdSingleTenantAccount ControlTower-->AWSIntegrationTestAccount ControlTower-->AWSPreviewAccount ControlTower-->AWSIndividualDeveloperAccount ControlTower-->AWSMonitoringAccount ControlTower-->AWSLogsAccount subgraph AWSProdMultiTenantAccount[AWS: Production Multi-tenant] AccountFillerProdMultiTenant[...] end subgraph AWSProdSingleTenantAccount[AWS: Production Single-tenant] AccountFillerProdSingleTenant[...] end subgraph AWSIntegrationTestAccount[AWS: Integration Test] AccountFillerIntegrationTest[...] end subgraph AWSPreviewAccount[AWS: Preview] AccountFillerPreview[...] end subgraph AWSIndividualDeveloperAccount[AWS: Individual Developer] AccountFillerIndividualDeveloper[...] end subgraph AWSMonitoringAccount[AWS: Monitoring] direction LR CloudWatchDashboards[CloudWatch Dashboards] CloudWatchMetrics[CloudWatch Metrics/Alarms] XRay[XRay Analytics] end subgraph AWSLogsAccount[AWS: Logs] CloudWatchLogs[CloudWatch Logs] end classDef container stroke:#333,stroke-width:2px,fill:transparent,padding:8px class ControlTower,AWSProdMultiTenantAccount,AWSProdSingleTenantAccount,AWSIntegrationTestAccount,AWSPreviewAccount,AWSIndividualDeveloperAccount,AWSMonitoringAccount,AWSLogsAccount container;
Each of the infrastructure accounts (Production, Integration, Developer) all hold the same services and follow the same setup. The infrastructure we will make might seem complex at first, and it is, but as we go through each piece everything will start to make sense.
The diagram gets quite large, so we will split it up into three parts:
Let's focus first on the Client to Frontend paths:
graph TD Client Route53 Client-->Route53 Route53-->FrontendCloudFront Route53-->InternalCloudFront Route53-->CertificateACM Frontend-->API Internal-->API subgraph Certificate[ACM: Certificate] CertificateACM end subgraph Frontend[Frontend: Public] FrontendCloudFront[CloudFront] FrontendS3App[S3: Static UI Files] FrontendCloudFront-->FrontendS3App end subgraph Internal[Frontend: Internal] InternalCloudFront[CloudFront] InternalCognito[Cognito] InternalS3App[S3: Static UI Files] InternalCloudFront-->InternalCognito InternalCloudFront-->InternalS3App end subgraph API APIFiller[...] end classDef container stroke:#333,stroke-width:2px,fill:transparent,padding:8px class Certificate,Frontend,Internal,API,Media,Database,Notification,Async,Monitoring container;
As we can see, the two Frontends need something to talk to, let's check out the APIs:
graph TD Client Route53 Client-->Route53 Route53-->APICloudFront subgraph API APICloudFront[CloudFront] APIWAF[WAF] APIAPIGateway[API Gateway] APILambdaAuthentication[Lambda: Custom Authorizer] APILambdaRouter[Lambda: GraphQL Supergraph
Apollo Router] APILambdaServiceReviews[Lambda: GraphQL Subgraph
Reviews Service] APILambdaServiceUsers[Lambda: GraphQL Subgraph
Users Service] APILambdaServiceProducts[Lambda: GraphQL Subgraph
Products Service] APICloudFront-->APIWAF-->APIAPIGateway APIAPIGateway--Cached-->APILambdaAuthentication APIAPIGateway-->APILambdaRouter APILambdaRouter-->APILambdaServiceReviews APILambdaRouter-->APILambdaServiceUsers APILambdaRouter-->APILambdaServiceProducts end subgraph Database DatabaseDynamoDB[DynamoDB] end %% APILambdaAuthentication-->Database APILambdaServiceReviews-->Database APILambdaServiceUsers-->Database APILambdaServiceProducts-->Database subgraph Monitoring[Monitoring] MonitoringXray[Xray] MonitoringCloudWatch[CloudWatch Metrics/Alarms] end API-->Monitoring classDef container stroke:#333,stroke-width:2px,fill:transparent,padding:8px class Certificate,Frontend,Internal,API,Media,Database,Notification,Async,Monitoring container;
And finally we can see the Media and Async work (the Database and some APIs reappear here as well):
graph TD Client Route53 Client-->Route53 Route53-->APICloudFront subgraph API APILambdaServiceReviews[Lambda: GraphQL Subgraph
Reviews Service] APILambdaServiceProducts[Lambda: GraphQL Subgraph
Products Service] end subgraph Media MediaConvert[MediaConvert] MediaS3[S3: Media Files
Image Bucket + Video Bucket] end %% FrontendCloudFront-->MediaS3 APILambdaServiceProducts--Create Signed URL-->MediaS3 Client--Upload via Signed URL-->MediaS3 APILambdaServiceProducts--Create Job-->MediaConvert subgraph Database DatabaseDynamoDB[DynamoDB] end subgraph Notification[Notification] NotificationSES[SES: Emails] NotificationSNS[SNS: Mobile Notification] end subgraph Async[Async Work] AsyncSQS[SQS] AsyncEventBridge[Event Bridge: Pub/Sub] AsyncLambdaAnalytics[Lambda: Analytics] AsyncLambdaNotification[Lambda: Notification] AsyncEventBridge-->AsyncLambdaAnalytics AsyncSQS-->AsyncLambdaNotification end DatabaseDynamoDB--Streams-->AsyncEventBridge AsyncLambdaAnalytics-->Database APILambdaServiceReviews-->AsyncSQS AsyncLambdaNotification-->Notification classDef container stroke:#333,stroke-width:2px,fill:transparent,padding:8px class Certificate,Frontend,Internal,API,Media,Database,Notification,Async,Monitoring container;
If we combine all the individual diagrams, we get:
graph TD Client Route53 Client-->Route53 Route53-->FrontendCloudFront Route53-->InternalCloudFront Route53-->APICloudFront Route53-->CertificateACM subgraph Certificate[ACM: Certificate] CertificateACM end subgraph Frontend[Frontend: Public] FrontendCloudFront[CloudFront] FrontendS3App[S3: Static UI Files] FrontendCloudFront-->FrontendS3App end subgraph Internal[Frontend: Internal] InternalCloudFront[CloudFront] InternalCognito[Cognito] InternalS3App[S3: Static UI Files] InternalCloudFront-->InternalCognito InternalCloudFront-->InternalS3App end subgraph API APICloudFront[CloudFront] APIWAF[WAF] APIAPIGateway[API Gateway] APILambdaAuthentication[Lambda: Custom Authorizer] APILambdaRouter[Lambda: GraphQL Supergraph
Apollo Router] APILambdaServiceTodo[Lambda: GraphQL Subgraph
Todo Service] APILambdaServiceMedia[Lambda: GraphQL Subgraph
Media Service] APICloudFront-->APIWAF-->APIAPIGateway APIAPIGateway--Cached-->APILambdaAuthentication APIAPIGateway-->APILambdaRouter APILambdaRouter-->APILambdaServiceTodo APILambdaRouter-->APILambdaServiceMedia end subgraph Media MediaConvert[MediaConvert] MediaS3[S3: Media Files
Image Bucket + Video Bucket] end %% FrontendCloudFront-->MediaS3 APILambdaServiceMedia--Create Signed URL-->MediaS3 Client--Upload via Signed URL-->MediaS3 APILambdaServiceMedia--Create Job-->MediaConvert subgraph Database DatabaseDynamoDB[DynamoDB] end %% APILambdaAuthentication-->Database APILambdaServiceTodo-->Database APILambdaServiceMedia-->Database subgraph Notification[Notification] NotificationSES[SES: Emails] NotificationSNS[SNS: Mobile Notification] end subgraph Async[Async Work] AsyncSQS[SQS] AsyncEventBridge[Event Bridge: Pub/Sub] AsyncLambdaAnalytics[Lambda: Analytics] AsyncLambdaNotification[Lambda: Notification] AsyncEventBridge-->AsyncLambdaAnalytics AsyncSQS-->AsyncLambdaNotification end DatabaseDynamoDB--Streams-->AsyncEventBridge AsyncLambdaAnalytics-->Database APILambdaServiceTodo-->AsyncSQS AsyncLambdaNotification-->Notification subgraph Monitoring[Monitoring] MonitoringXray[Xray] MonitoringCloudWatch[CloudWatch Metrics/Alarms] end API-->Monitoring classDef container stroke:#333,stroke-width:2px,fill:transparent,padding:8px class Certificate,Frontend,Internal,API,Media,Database,Notification,Async,Monitoring container;
Next up is to start building! Follow along in Part 1 of the series here.