AWSTemplateFormatVersion: "2010-09-09" Transform: 'AWS::Serverless-2016-10-31' Parameters: Environment: Type: String Default: dev Description: Environment name LogLevel: Type: String Default: INFO RetentionInDays: Type: Number Default: 30 Description: CloudWatch Logs retention period for Lambda functions EventBusArn: Type: AWS::SSM::Parameter::Value<String> Description: EventBridge Event Bus ARN EventBusName: Type: AWS::SSM::Parameter::Value<String> Description: EventBridge Event Bus Name Globals: Function: Runtime: python3.9 Architectures: - arm64 Handler: main.handler Timeout: 30 Tracing: Active Environment: Variables: ENVIRONMENT: !Ref Environment EVENT_BUS_NAME: !Ref EventBusName METADATA_KEY: "__metadata" TABLE_NAME: !Ref Table POWERTOOLS_SERVICE_NAME: warehouse POWERTOOLS_TRACE_DISABLED: "false" LOG_LEVEL: !Ref LogLevel Layers: - !Sub "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension-Arm64:1" Resources: ######### # TABLE # ######### Table: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: orderId AttributeType: S - AttributeName: productId AttributeType: S - AttributeName: newDate AttributeType: S BillingMode: PAY_PER_REQUEST KeySchema: - AttributeName: orderId KeyType: HASH - AttributeName: productId KeyType: RANGE GlobalSecondaryIndexes: - IndexName: product KeySchema: - AttributeName: productId KeyType: HASH - AttributeName: orderId KeyType: RANGE Projection: ProjectionType: ALL - IndexName: orderId-new KeySchema: - AttributeName: orderId KeyType: HASH - AttributeName: newDate KeyType: RANGE Projection: ProjectionType: ALL StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES TableParameter: Type: AWS::SSM::Parameter Properties: Name: !Sub /ecommerce/${Environment}/warehouse/table/name Type: String Value: !Ref Table ############# # FUNCTIONS # ############# OnOrderEventsFunction: Type: AWS::Serverless::Function Properties: CodeUri: src/on_order_events/ Events: OrdersCreatedOrDeleted: Type: CloudWatchEvent Properties: EventBusName: !Ref EventBusName Pattern: # Capture Created and Deleted events source: [ecommerce.orders] detail-type: - OrderCreated - OrderDeleted OrdersModified: Type: CloudWatchEvent Properties: EventBusName: !Ref EventBusName Pattern: # Capture Modified events if the products have changed source: [ecommerce.orders] detail-type: - OrderModified detail: changed: [products] EventInvokeConfig: # Put failed events on a DLQ DestinationConfig: OnFailure: Type: SQS Destination: !GetAtt DeadLetterQueue.Outputs.QueueArn Policies: - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy - DynamoDBCrudPolicy: TableName: !Ref Table OnOrderEventsLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub "/aws/lambda/${OnOrderEventsFunction}" RetentionInDays: !Ref RetentionInDays TableUpdateFunction: Type: AWS::Serverless::Function Properties: Handler: main.handler CodeUri: src/table_update/ Events: DynamoDB: Type: DynamoDB Properties: Stream: !GetAtt Table.StreamArn StartingPosition: TRIM_HORIZON DestinationConfig: OnFailure: Destination: !GetAtt DeadLetterQueue.Outputs.QueueArn Policies: - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy - Version: "2012-10-17" Statement: - Effect: Allow Action: - events:PutEvents Resource: !Ref EventBusArn Condition: StringEquals: events:source: "ecommerce.warehouse" - Effect: Allow Action: - dynamodb:Query Resource: !GetAtt Table.Arn - Effect: Allow Action: - sqs:SendMessage Resource: !GetAtt DeadLetterQueue.Outputs.QueueArn TableUpdateLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub "/aws/lambda/${TableUpdateFunction}" RetentionInDays: !Ref RetentionInDays ##################### # DEAD LETTER QUEUE # ##################### DeadLetterQueue: Type: AWS::CloudFormation::Stack Properties: # The path starts with '../..' as this will be evaluated from the # warehouse/build folder, not the warehouse folder. TemplateURL: ../../shared/templates/dlq.yaml ############# # DASHBOARD # ############# Dashboard: Type: AWS::CloudWatch::Dashboard Properties: DashboardName: !Ref AWS::StackName DashboardBody: !Sub | { "start": "-PT6H", "periodOverride": "inherit", "widgets": [ { "type": "text", "x": 0, "y": 0, "width": 20, "height": 1, "properties": { "markdown": "\n# Key Metrics\n" } }, { "type": "metric", "x": 0, "y": 1, "width": 12, "height": 3, "properties": { "metrics": [ ["ecommerce.warehouse", "packageCreated", "environment", "${Environment}", "service", "warehouse", {"color": "#66bb6a", "label": "Package Created"}], ["ecommerce.warehouse", "packagingFailed", "environment", "${Environment}", "service", "warehouse", {"color": "#ef5350", "label": "Packaging Failed"}] ], "view": "singleValue", "period": 86400, "stacked": false, "stat": "Sum", "region": "${AWS::Region}", "title": "Packages" } }, { "type": "metric", "x": 12, "y": 1, "width": 12, "height": 3, "properties": { "metrics": [ [{"expression": "le1+le2+lt1+lt2", "label": "Lambda Errors", "color": "#66bb6a"}], [{"expression": "db1+db2", "label": "DynamoDB Errors", "color": "#ffa726"}], ["AWS/Lambda", "Errors", "FunctionName", "${OnOrderEventsFunction}", {"id": "le1", "visible": false}], ["AWS/Lambda", "Errors", "FunctionName", "${TableUpdateFunction}", {"id": "le2", "visible": false}], ["AWS/Lambda", "Throttles", "FunctionName", "${OnOrderEventsFunction}", {"id": "lt1", "visible": false}], ["AWS/Lambda", "Throttles", "FunctionName", "${TableUpdateFunction}", {"id": "lt2", "visible": false}], ["AWS/DynamoDB", "UserErrors", "TableName", "${Table}", {"id": "db1", "visible": false}], ["AWS/DynamoDB", "SystemErrors", "TableName", "${Table}", {"id": "db2", "visible": false}] ], "view": "singleValue", "period": 86400, "stacked": false, "stat": "Sum", "region": "${AWS::Region}", "title": "Errors" } }, { "type": "text", "x": 0, "y": 7, "width": 24, "height": 1, "properties": { "markdown": "\n# Traffic\n" } }, { "type": "metric", "x": 0, "y": 8, "width": 12, "height": 6, "properties": { "metrics": [ ["AWS/Lambda", "Invocations", "FunctionName", "${OnOrderEventsFunction}", {"color": "#29b6f6", "label": "OnOrderEvents"}], ["AWS/Lambda", "Invocations", "FunctionName", "${TableUpdateFunction}", {"color": "#ffa726", "label": "TableUpdate"}] ], "view": "timeSeries", "period": 60, "stacked": false, "stat": "Sum", "region": "${AWS::Region}", "title": "Requests" } }, { "type": "metric", "x": 12, "y": 8, "width": 12, "height": 6, "properties": { "metrics": [ ["ecommerce.warehouse", "packageCreated", "environment", "${Environment}", "service", "warehouse", {"color": "#66bb6a", "label": "Package Created"}], ["ecommerce.warehouse", "packagingFailed", "environment", "${Environment}", "service", "warehouse", {"color": "#ef5350", "label": "Packaging Failed"}] ], "view": "timeSeries", "period": 60, "stacked": false, "stat": "Sum", "region": "${AWS::Region}", "title": "Packages" } }, { "type": "text", "x": 0, "y": 14, "width": 24, "height": 1, "properties": { "markdown": "\n# Latency and Duration\n" } }, { "type": "metric", "x": 0, "y": 15, "width": 10, "height": 3, "properties": { "metrics": [ ["AWS/Lambda", "Duration", "FunctionName", "${OnOrderEventsFunction}", {"color": "#9ccc65", "label": "p50"}], ["...", {"stat": "p90", "color": "#ffee58", "label": "p90"}], ["...", {"stat": "p99", "color": "#ef5350", "label": "p99"}] ], "view": "singleValue", "period": 86400, "stacked": false, "stat": "p50", "region": "${AWS::Region}", "title": "OnOrderEvents Latency" } }, { "type": "metric", "x": 10, "y": 15, "width": 10, "height": 3, "properties": { "metrics": [ ["AWS/Lambda", "Duration", "FunctionName", "${TableUpdateFunction}", {"color": "#9ccc65", "label": "p50"}], ["...", {"stat": "p90", "color": "#ffee58", "label": "p90"}], ["...", {"stat": "p99", "color": "#ef5350", "label": "p99"}] ], "view": "singleValue", "period": 86400, "stacked": false, "stat": "p50", "region": "${AWS::Region}", "title": "TableUpdate Latency" } }, { "type": "metric", "x": 0, "y": 21, "width": 10, "height": 6, "properties": { "metrics": [ ["AWS/Lambda", "Duration", "FunctionName", "${OnOrderEventsFunction}", {"color": "#9ccc65", "label": "p50"}], ["...", {"stat": "p90", "color": "#ffee58", "label": "p90"}], ["...", {"stat": "p99", "color": "#ef5350", "label": "p99"}] ], "view": "timeSeries", "period": 60, "stacked": false, "stat": "p50", "region": "${AWS::Region}", "title": "OnOrderEvents Duration" } }, { "type": "metric", "x": 10, "y": 21, "width": 10, "height": 6, "properties": { "metrics": [ ["AWS/Lambda", "Duration", "FunctionName", "${TableUpdateFunction}", {"color": "#9ccc65", "label": "p50"}], ["...", {"stat": "p90", "color": "#ffee58", "label": "p90"}], ["...", {"stat": "p99", "color": "#ef5350", "label": "p99"}] ], "view": "timeSeries", "period": 60, "stacked": false, "stat": "p50", "region": "${AWS::Region}", "title": "TableUpdate Duration" } }, { "type": "metric", "x": 0, "y": 22, "width": 12, "height": 6, "properties": { "metrics": [ ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "GetItem", {"color": "#66bb6a", "label": "GetItem"}], ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "PutItem", {"color": "#ffa726", "label": "PutItem"}], ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "UpdateItem", {"color": "#29b6f6", "label": "UpdateItem"}], ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "DeleteItem", {"color": "#ef5350", "label": "DeleteItem"}], ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "Query", {"color": "#ec407a", "label": "Query"}] ], "view": "timeSeries", "period": 60, "stacked": false, "stat": "Average", "region": "${AWS::Region}", "title": "DynamoDB Latency" } }, { "type": "metric", "x": 12, "y": 22, "width": 12, "height": 6, "properties": { "metrics": [ ["AWS/Lambda", "Duration", "FunctionName", "${OnOrderEventsFunction}", {"color": "#ec407a", "label": "OnOrderEvents P90"}], ["AWS/Lambda", "Duration", "FunctionName", "${TableUpdateFunction}", {"color": "#ffa726", "label": "TableUpdate P90"}], ["AWS/Lambda", "IteratorAge", "FunctionName", "${TableUpdateFunction}", {"color": "#ff7043", "stat": "Average", "label": "TableUpdate Iterator Age"}] ], "view": "timeSeries", "period": 60, "stacked": false, "stat": "p90", "region": "${AWS::Region}", "title": "Asynchronous Durations" } }, { "type": "text", "x": 0, "y": 23, "width": 24, "height": 1, "properties": { "markdown": "\n# Errors\n" } }, { "type": "metric", "x": 0, "y": 24, "width": 8, "height": 6, "properties": { "metrics": [ ["AWS/Lambda", "Errors", "FunctionName", "${OnOrderEventsFunction}", {"color": "#29b6f6", "label": "OnOrderEvents"}], ["AWS/Lambda", "Errors", "FunctionName", "${TableUpdateFunction}", {"color": "#ffa726", "label": "TableUpdate"}] ], "view": "timeSeries", "period": 60, "stacked": false, "stat": "Sum", "region": "${AWS::Region}", "title": "Lambda Errors" } }, { "type": "metric", "x": 8, "y": 24, "width": 8, "height": 6, "properties": { "metrics": [ ["AWS/DynamoDB", "UserErrors", "TableName", "${Table}", {"color": "#ffa726", "label": "User Errors"}], ["AWS/DynamoDB", "SystemErrors", "TableName", "${Table}", {"color": "#ef5350", "label": "System Errors"}] ], "view": "timeSeries", "period": 60, "stacked": false, "stat": "Sum", "region": "${AWS::Region}", "title": "DynamoDB Errors" } }, { "type": "log", "x": 0, "y": 29, "width": 24, "height": 6, "properties": { "query": "SOURCE '/aws/lambda/${OnOrderEventsFunction}' | SOURCE '/aws/lambda/${TableUpdateFunction}' | fields @message \n | filter @message like /ERROR/ \n | sort @timestamp desc\n| limit 20\n", "region": "${AWS::Region}", "stacked": false, "view": "table", "title": "Last 20 errors" } } ] }