
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.