----- ./billing/template.yaml # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > billing SAM Template for the billing microservice Parameters: EventBusName: Description: The name of the EventBridge custom event bus to listen on Type: String Default: AnyCompany PublishEventsFunctionArn: Description: The ARN of the Lambda proxy function which puts events onto your custom event bus Type: String AllowedPattern: "^arn:aws:lambda:[a-zA-Z0-9_.-]+:[0-9]{12}:function:[a-zA-Z0-9_.-]+" Resources: ################################################################################ # Amazon EventBridge rule ################################################################################ TransactionRule: Type: AWS::Events::Rule Properties: Description: Listen on the custom event bus for TransactionInitiated events EventBusName: !Ref EventBusName EventPattern: source: - com.anycompany detail-type: - transaction-initiated Targets: # Amazon Kinesis Data Firehose for OLAP - Arn: !GetAtt TransactionFirehose.Arn Id: TransactionFirehose RoleArn: !GetAtt PublishToFirehoseRole.Arn # AWS Step Functions Standard Workflow for OLTP - Arn: !Ref TransactionWorkflow Id: TransactionWorkflow RoleArn: !GetAtt InvokeTransactionWorkflowRole.Arn InputTransformer: InputPathsMap: transaction_id: "$.id" customer_id: "$.detail.customer-id" received_datetime: "$.time" requested_datetime: "$.detail.initiated-at" source_account: "$.detail.from-account" destination_account: "$.detail.to-account" total_amount: "$.detail.transaction-amount" InputTemplate: > { "transaction_id": , "customer_id": , "received_datetime": , "requested_datetime": , "source_account": , "destination_account": , "total_amount": } ################################################################################ # Amazon S3 buckets ################################################################################ ## Raw data bucket RawDataBucket: Type: AWS::S3::Bucket ## Converted data bucket ProcessedDataBucket: Type: AWS::S3::Bucket ################################################################################ # AWS Glue database and table ################################################################################ ## Glue Database TransactionGlueDatabase: Type: AWS::Glue::Database Properties: CatalogId: !Ref AWS::AccountId DatabaseInput: Name: app2025 ## Glue Table TransactionGlueTable: Type: AWS::Glue::Table Properties: CatalogId: !Ref AWS::AccountId DatabaseName: !Ref TransactionGlueDatabase TableInput: Name: transactions PartitionKeys: - Name: year Type: string - Name: month Type: string - Name: day Type: string - Name: hour Type: string Retention: 0 TableType: EXTERNAL_TABLE StorageDescriptor: Columns: - Name: transaction_id Type: string - Name: customer_id Type: string - Name: received_datetime Type: string - Name: requested_datetime Type: string - Name: source_account Type: string - Name: destination_account Type: string - Name: total_amount Type: double InputFormat: org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat OutputFormat: org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat Compressed: true NumberOfBuckets: -1 SerdeInfo: SerializationLibrary: org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe Parameters: serialization.format: '1' BucketColumns: [] SortColumns: [] StoredAsSubDirectories: false ################################################################################ # Amazon Kinesis Data Firehose ################################################################################ ## Transaction Firehose TransactionFirehose: Type: AWS::KinesisFirehose::DeliveryStream Properties: DeliveryStreamType: DirectPut ExtendedS3DestinationConfiguration: BucketARN: !GetAtt ProcessedDataBucket.Arn Prefix: !Join - '' - - !Ref TransactionGlueTable - '/year=!{timestamp:YYYY}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/' ErrorOutputPrefix: !Join - '' - - !Ref TransactionGlueTable - 'error/!{firehose:error-output-type}/year=!{timestamp:YYYY}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/' CompressionFormat: UNCOMPRESSED RoleARN: !GetAtt TransactionFirehoseRole.Arn DataFormatConversionConfiguration: Enabled: true InputFormatConfiguration: Deserializer: HiveJsonSerDe: {} OutputFormatConfiguration: Serializer: ParquetSerDe: Compression: GZIP SchemaConfiguration: CatalogId: !Ref AWS::AccountId DatabaseName: !Ref TransactionGlueDatabase Region: !Ref AWS::Region RoleARN: !GetAtt TransactionFirehoseRole.Arn TableName: !Ref TransactionGlueTable VersionId: LATEST BufferingHints: IntervalInSeconds: 60 SizeInMBs: 64 S3BackupMode: Enabled S3BackupConfiguration: BucketARN: !GetAtt RawDataBucket.Arn CompressionFormat: GZIP RoleARN: !GetAtt TransactionFirehoseRole.Arn BufferingHints: IntervalInSeconds: 60 SizeInMBs: 1 ################################################################################ # Amazon DynamoDB table ################################################################################ TransactionTable: Type: AWS::Serverless::SimpleTable ################################################################################ # AWS Step Functions Standard Workflow ################################################################################ TransactionWorkflow: Description: Standard Workflow for normalizing and cleaning newly-created accounts Type: AWS::StepFunctions::StateMachine Properties: StateMachineName: TransactionProcess RoleArn: !GetAtt TransactionWorkflowRole.Arn StateMachineType: STANDARD DefinitionString: !Sub - |- { "Comment": "An example of the Amazon States Language using a DynamoDB service integration.", "StartAt": "Write to DynamoDB", "States": { "Write to DynamoDB": { "Type": "Task", "Resource": "arn:aws:states:::dynamodb:putItem", "Parameters": { "TableName": "${TransactionTable}", "Item": { "id": {"S.$": "$.transaction_id"}, "customer_id": {"S.$": "$.customer_id"}, "received_datetime": {"S.$": "$.received_datetime"}, "requested_datetime": {"S.$": "$.requested_datetime"}, "source_account": {"S.$": "$.source_account"}, "destination_account": {"S.$": "$.destination_account"}, "total_amount": {"S.$": "$.total_amount"} } }, "ResultPath": "$.DynamoDB", "Next": "Publish TransactionProcessed Event" }, "Publish TransactionProcessed Event": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "Parameters": { "FunctionName": "${PublishEventsFunctionArn}", "Payload": { "EventBusName": "${EventBusName}", "Source": "com.anycompany", "DetailType": "transaction-processed", "Detail": { "transaction_id": {"S.$": "$.transaction_id"}, "customer_id": {"S.$": "$.customer_id"}, "received_datetime": {"S.$": "$.received_datetime"}, "requested_datetime": {"S.$": "$.requested_datetime"}, "source_account": {"S.$": "$.source_account"}, "destination_account": {"S.$": "$.destination_account"}, "total_amount": {"S.$": "$.total_amount"} } } }, "End": true } } } - { EventBusName: !Ref EventBusName, PublishEventsFunctionArn: !Ref PublishEventsFunctionArn, DynamoDBTable: !Ref TransactionTable } ################################################################################ # AWS IAM resources ################################################################################ ## Transaction Firehose IAM Role TransactionFirehoseRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: "firehose.amazonaws.com" Action: - "sts:AssumeRole" Condition: StringEquals: sts:ExternalId: !Ref AWS::AccountId Policies: - PolicyName: S3WritePolicy PolicyDocument: Version: '2012-10-17' Statement: Action: - 's3:AbortMultipartUpload' - 's3:GetBucketLocation' - 's3:GetObject' - 's3:ListBucket' - 's3:ListBucketMultipartUploads' - 's3:PutObject' Effect: Allow Resource: - !GetAtt RawDataBucket.Arn - !Join - '' - - 'arn:aws:s3:::' - !Ref RawDataBucket - '*' - !GetAtt ProcessedDataBucket.Arn - !Join - '' - - 'arn:aws:s3:::' - !Ref ProcessedDataBucket - '*' - PolicyName: GluePolicy PolicyDocument: Version: '2012-10-17' Statement: Action: - glue:GetTable - glue:GetTableVersion - glue:GetTableVersions Effect: Allow Resource: - !Join - '' - - 'arn:aws:glue:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':table/' - !Ref TransactionGlueDatabase - '/' - !Ref TransactionGlueTable - !Join - '' - - 'arn:aws:glue:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':database/' - !Ref TransactionGlueDatabase - !Join - '' - - 'arn:aws:glue:' - !Ref AWS::Region - ':' - !Ref AWS::AccountId - ':catalog' ## EventBridge Rule IAM Role for putting records into Firehose PublishToFirehoseRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: TransactionFirehosePolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - firehose:PutRecord - firehose:PutRecordBatch Resource: - !GetAtt TransactionFirehose.Arn ## Standard Workflow IAM Role TransactionWorkflowRole: Description: IAM Role for our Expired Subscription workflow Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - !Sub states.${AWS::Region}.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: WriteToDynamoDBPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - dynamoDB:PutItem Resource: - !GetAtt TransactionTable.Arn - PolicyName: PublishEventsPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - lambda:InvokeFunction Resource: - !Ref PublishEventsFunctionArn ## EventBridge Rule IAM Role for invoking Express Workflow InvokeTransactionWorkflowRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: TransactionWorkflowPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - states:StartExecution Resource: - !Ref TransactionWorkflow ----- ./customer/template.yaml # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > customer SAM Template for the customer microservice Parameters: EventBusName: Description: The name of the EventBridge custom event bus to listen on Type: String Default: AnyCompany PublishEventsFunctionArn: Description: The ARN of the Lambda proxy function which puts events onto your custom event bus Type: String AllowedPattern: "^arn:aws:lambda:[a-zA-Z0-9_.-]+:[0-9]{12}:function:[a-zA-Z0-9_.-]+" Resources: ################################################################################ # Standard Workflow example ################################################################################ AccountCreatedRule: Type: AWS::Events::Rule Properties: Description: Listen on the custom event bus for AccountCreated events EventBusName: !Ref EventBusName EventPattern: source: - com.anycompany detail-type: - account-created # State: ENABLED Targets: - Arn: !Ref NormalizeAccountWorkflow Id: NormalizeAccountWorkflow RoleArn: !GetAtt InvokeWorkflowRole.Arn NormalizeAccountWorkflow: Description: Standard Workflow for normalizing and cleaning newly-created accounts Type: AWS::StepFunctions::StateMachine Properties: StateMachineName: NormalizeAccountProcess RoleArn: !GetAtt NormalizeAccountRole.Arn StateMachineType: STANDARD DefinitionString: !Sub - |- { "Comment": "An example of the Amazon States Language using a choice state.", "StartAt": "Add to CRM", "States": { "Add to CRM": { "Type": "Pass", "Result": { "customer_id": "1auNYzW4PFkLxcxexCCX2bcWgDx" }, "ResultPath": "$.detail.crm_data", "Next": "Send for Normalization" }, "Send for Normalization": { "Type": "Task", "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken", "Parameters": { "QueueUrl": "${AccountCreationQueueUrl}", "MessageBody": { "Message.?": "$.detail.crm_data", "TaskToken.$": "$$.Task.Token" } }, "ResultPath": "$.detail.normalizationResults", "Next": "Publish AccountNormalized Event" }, "Publish AccountNormalized Event": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "Parameters": { "FunctionName": "${PublishEventsFunctionArn}", "Payload": { "EventBusName": "${EventBusName}", "Source.$": "$.source", "DetailType": "account-normalized", "Detail.$": "$.detail" } }, "End": true } } } - { EventBusName: !Ref EventBusName, PublishEventsFunctionArn: !Ref PublishEventsFunctionArn, AccountCreationQueueUrl: !Ref AccountCreationQueue } NormalizeAccountRole: Description: IAM Role for our Normalize Account workflow Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - !Sub states.${AWS::Region}.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SendToQueue PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - sqs:SendMessage Resource: - !GetAtt AccountCreationQueue.Arn - PolicyName: PublishEventsPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - lambda:InvokeFunction Resource: - !Ref PublishEventsFunctionArn AccountCreationQueue: Description: SQS queue for processing accounts that need to be normalized. Type: AWS::SQS::Queue ################################################################################ # Express Workflow example ################################################################################ ExpiredSubscriptionRule: Type: AWS::Events::Rule Properties: Description: Listen on the custom event bus for SubscriptionExpired events EventBusName: !Ref EventBusName EventPattern: source: - com.anycompany detail-type: - subscription-expired # State: ENABLED Targets: - Arn: !Ref ExpiredSubscriptionWorkflow Id: ExpiredSubscriptionWorkflow RoleArn: !GetAtt InvokeWorkflowRole.Arn ExpiredSubscriptionWorkflow: Description: Express Workflow for processing expired subscriptions Type: AWS::StepFunctions::StateMachine Properties: StateMachineName: ExpiredSubscriptionProcess RoleArn: !GetAtt ExpiredSubscriptionRole.Arn StateMachineType: EXPRESS LoggingConfiguration: Destinations: - CloudWatchLogsLogGroup: LogGroupArn: !GetAtt AppLogGroup.Arn IncludeExecutionData: true Level: ALL DefinitionString: !Sub - |- { "Comment": "A Hello World example demonstrating various state types of the Amazon States Language", "StartAt": "Process Cancellation", "States": { "Process Cancellation": { "Type": "Parallel", "ResultPath": "$.ProcessCancellationResults", "Branches": [ { "StartAt": "Suspend All Resources", "States": { "Suspend All Resources": { "Type": "Pass", "ResultPath": "$.SuspendResourcesResult", "End": true } } }, { "StartAt": "Send Expiration Email", "States": { "Send Expiration Email": { "Type": "Pass", "ResultPath": "$.SendExpirationEmailResult", "End": true } } } ], "Next": "Publish ExpirationProcessed Event" }, "Publish ExpirationProcessed Event": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "Parameters": { "FunctionName": "${PublishEventsFunctionArn}", "Payload": { "EventBusName": "${EventBusName}", "Source.$": "$.source", "DetailType": "expiration-processed", "Detail.$": "$.detail" } }, "End": true } } } - { EventBusName: !Ref EventBusName, PublishEventsFunctionArn: !Ref PublishEventsFunctionArn } AppLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: /customer RetentionInDays: 7 # optional ExpiredSubscriptionRole: Description: IAM Role for our Expired Subscription workflow Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - !Sub states.${AWS::Region}.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: ExpressWorkflowLogsPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - logs:CreateLogDelivery - logs:CreateLogGroup - logs:DeleteLogDelivery - logs:DescribeResourcePolicies - logs:GetLogDelivery - logs:ListLogDeliveries - logs:PutResourcePolicy - logs:UpdateLogDelivery Resource: # These actions do not support resource-level permissions. # Policies granting access must specify "*" in the resource element. - "*" - Effect: Allow Action: - logs:CreateLogStream - logs:DescribeLogGroups - logs:DescribeLogStreams - logs:PutLogEvents Resource: # AWS::Logs::LogGroup.Arn ends with ":*" so it matches: # - log-group (required by logs:CreateLogStream, logs:DescribeLogGroups, and logs:DescribeLogStreams) # - log-stream (required by logs:PutLogEvents) - !GetAtt AppLogGroup.Arn - PolicyName: PublishEventsPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - lambda:InvokeFunction Resource: - !Ref PublishEventsFunctionArn ################################################################################ # Shared role allowing Amazon EventBridge rules to invoke AWS Step Functions # workflows in the Customer microservice ################################################################################ InvokeWorkflowRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: InvokeCustomerWorkflowsPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - states:StartExecution Resource: - !Ref ExpiredSubscriptionWorkflow - !Ref NormalizeAccountWorkflow ################################################################################ # CloudFormation outputs for use in other stacks. ################################################################################ Outputs: AccountQueueArn: Description: The URL of the AccountCreationQueue for deploying the simulator app. Value: !GetAtt AccountCreationQueue.Arn StateMachineArn: Description: The ARN of the NormalizeAccountWorkflow for deploying the simulator app. Value: !Ref NormalizeAccountWorkflow ----- ./infrastructure/publish-events/app.js // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 const AWS = require('aws-sdk') const eventbridge = new AWS.EventBridge() exports.lambdaHandler = async (event, context) => { const params = { Entries: [ { EventBusName: event.EventBusName, Source: event.Source, DetailType: event.DetailType, Time: new Date(), // Main event body Detail: JSON.stringify(event.Detail) } ] } console.log(params) const result = await eventbridge.putEvents(params).promise() if (result.FailedEntryCount > 0) return ({Error: "Failed to push event onto the event bus", Entry: params.Entries[0], Result: result}) else return ({Entry: params.Entries[0], EventId: result.Entries[0].EventId}) }; ----- ./infrastructure/publish-events/package.json { "name": "publish-events", "version": "0.1.0", "description": "Publish events onto a given Amazon EventBridge event bus", "main": "app.js", "author": "SAM CLI", "license": "MIT-0" } ----- ./infrastructure/template.yaml # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > AnyCompany Amazon EventBridge event bus EventBus declaration for the AnyCompany sample SaaS application. Resources: EventBus: Type: AWS::Events::EventBus Properties: Name: AnyCompany EventBusSSMParameter: Type: AWS::SSM::Parameter Properties: Description: The ARN of the event bus for other services to consume Name: /EventBusARN Type: String Value: !GetAtt EventBus.Arn PublishEventsFunction: Type: AWS::Serverless::Function Properties: CodeUri: publish-events/ Handler: app.lambdaHandler Runtime: nodejs12.x Policies: - Statement: - Effect: Allow Action: - events:PutEvents Resource: - !GetAtt EventBus.Arn Outputs: EventBusArn: Description: "The ARN of the event bus" Value: !GetAtt EventBus.Arn EventBusName: Description: "The name of the event bus" Value: !GetAtt EventBus.Name PublishEventsFunctionArn: Description: "The ARN of our Lambda function" Value: !GetAtt PublishEventsFunction.Arn ----- ./operations/template.yaml # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > operations SAM Template for the operations microservice Parameters: EventBusName: Description: The name of the EventBridge custom event bus to listen on Type: String Default: AnyCompany Resources: LegacyOperationRule: Type: AWS::Events::Rule Properties: Description: Listen on the custom event bus for OperationPerformed events EventBusName: !Ref EventBusName EventPattern: source: - com.anycompany detail-type: - operation-performed Targets: - Arn: !Ref LegacyOperationTopic Id: LegacyOperationTopic LegacyOperationTopic: Type: AWS::SNS::Topic Properties: TopicName: LegacyOperationTopic Subscription: - Endpoint: !GetAtt LegacyOperationQueue.Arn Protocol: sqs LegacyOperationQueue: Type: AWS::SQS::Queue Properties: QueueName: LegacyOperationQueue LegacyOperationQueuePolicy: Type: AWS::SQS::QueuePolicy Properties: Queues: - !Ref LegacyOperationQueue PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - sqs:SendMessage Principal: AWS: - "*" Resource: - !GetAtt LegacyOperationQueue.Arn Condition: ForAllValues:ArnEquals: aws:SourceArn: !Ref LegacyOperationTopic Outputs: QueueEndpoint: Description: "SQS Queue Endpoint" Value: !Ref LegacyOperationQueue TopicArn: Description: "SNS Topic ARN" Value: !Ref LegacyOperationTopic ----- ./simulator/simulate-approval/app.js // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 console.log('Loading function'); const aws = require('aws-sdk'); exports.lambdaHandler = (event, context, callback) => { const stepfunctions = new aws.StepFunctions(); for (const record of event.Records) { const messageBody = JSON.parse(record.body); const taskToken = messageBody.TaskToken; const params = { output: "\"Callback task completed successfully.\"", taskToken: taskToken }; console.log(`Calling Step Functions to complete callback task with params ${JSON.stringify(params)}`); stepfunctions.sendTaskSuccess(params, (err, data) => { if (err) { console.error(err.message); callback(err.message); return; } console.log(data); callback(null); }); } }; ----- ./simulator/simulate-approval/package.json { "name": "simulate-approval", "version": "0.1.0", "description": "Simulate human approval of a transaction in an AWS Step Functions Standard Workflow", "main": "app.js", "author": "SAM CLI", "license": "MIT-0" } ----- ./simulator/simulate-transaction/app.js // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 const AWS = require('aws-sdk') const faker = require('faker') const { performance } = require('perf_hooks') const eventbridge = new AWS.EventBridge() exports.lambdaHandler = async () => { let tps = process.env.DESIRED_TPS for (let i = 0; i < 60; i++) { let entries = [] let t1 = performance.now() // Original (fails when tps > 10) // for (let j = 0; j < tps; j++) { // let transaction = generateTransaction("AnyCompany", "com.anycompany") // entries.push(transaction) // } let batch for (let j = 0; j < tps; j++) { if (j % 10 == 0) batch = [] let transaction = generateTransaction("AnyCompany", "com.anycompany") batch.push(transaction) if (j % 9 == 0 || j == tps - 1) entries.push(batch) } for (let i = 0; i < entries.length; i++) { let params = { Entries: entries[i], } const result = await eventbridge.putEvents(params).promise() if (result.FailedEntryCount > 0) return ({Error: "Failed to push event onto the event bus", Entry: params.Entries[0], Result: result}) } let t2 = performance.now() let sleepyTime = 1000 - (t2 - t1) if (i < 59) await sleep(sleepyTime) } return }; function generateTransaction(eventbus, source) { return { EventBusName: eventbus, Source: source, Time: new Date().toISOString(), DetailType: "transaction-initiated", Detail: JSON.stringify({ 'customer-id': faker.random.uuid(), 'initiated-at': new Date().toISOString(), 'from-account': faker.finance.iban(), 'to-account': faker.finance.iban(), 'transaction-amount': faker.finance.amount(), }), } } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ----- ./simulator/simulate-transaction/package.json { "name": "simulate-transaction", "version": "0.1.0", "description": "Transaction simulator", "main": "app.js", "author": "SAM CLI", "license": "MIT-0", "dependencies": { "faker": "^4.1.0" } } ----- ./simulator/template.yaml # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > simulator SAM Template for simulating human interaction in Episode 3. Parameters: AccountQueueArn: Description: ARN for the Account Approval SQS Queue Type: String AllowedPattern: "^arn:aws:sqs:[a-zA-Z0-9_.-]+:[0-9]{12}:[a-zA-Z0-9_.-]+" StateMachineArn: Description: ARN for the AWS Step Functions workflow we call back to Type: String AllowedPattern: "^arn:aws:states:[a-zA-Z0-9_.-]+:[0-9]{12}:stateMachine:[a-zA-Z0-9_.-]+" # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 3 Resources: SimulateApprovalFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: simulate-approval/ Handler: app.lambdaHandler Runtime: nodejs12.x Events: AccountPoller: Type: SQS Properties: Queue: !Ref AccountQueueArn Policies: - Statement: - Effect: Allow Action: - states:SendTaskSuccess - states:SendTaskFailure - states:SendTaskHeartbeat Resource: - !Ref StateMachineArn Outputs: SimulateApprovalFunction: Description: "Simulate Approval Lambda Function ARN" Value: !GetAtt SimulateApprovalFunction.Arn ----- README.md # App 2025 Want to see how everyone will build [serverless applications][serverless] on [AWS][aws] in 2025? Want to use those patterns today to gain a competitive advantage? Come learn how, with App 2025! Every Thursday we'll build a bit of our application live on [Twitch][twitch], so you can see the patterns that will help you get ahead today. ## Past sessions: ### Amazon EventBridge as the backbone of your app In the [first episode][first-episode] of App 2025, viewers learn a high-level pattern for building serverless applications. A sample use case is presented: an order fulfillment application for AnyCompany , a fictional Software as a Service (SaaS) company. [Amazon EventBridge][eventbridge] is introduced as the backbone of the app. [AWS Step Functions][step-functions] workflows, both standard and express, are introduced for business processes that consume events from EventBridge. Service integrations are introduced for data storage, and [Identity and Access Management (IAM)][iam] considerations are discussed. Monitoring and auditability of events on EventBridge are discussed. Finally, [AWS SAM CLI][sam-cli] is introduced, and a simple template is created with a single resource, a custom EventBridge event bus. ### Express Workflows for quick-running processes In the [second episode][second-episode] of App 2025, viewers learn a second high-level pattern for building serverless applications. AWS Step Functions [Express Workflows][express-workflows] are introduced for building high-volume, short-duration workflows. Express and Standard Workflows are compared and a rubric is provided for when to select each. The workflow event loop pattern is introduced: an event on an EventBridge event bus satisfies a rule and invokes an Express Workflow as a target. Once the workflow completes, a “ProcessCompleted” event is placed back onto the bus for further processing. ### Waiting for interactions with callbacks In the [third episode][third-episode] of App 2025, viewers learn how to pause execution of AWS Step Functions workflows using [callbacks][callbacks]. AWS Step Functions [Standard Workflows][standard-workflows] are introduced for building lower-volume, longer-running workflows. The _.waitTaskToken_ callback pattern is introduced for tasks that involve human interaction, are longer-running, or are cost-sensitive. Supported services are introduced, and _SendTaskSuccess_, _SendTaskFailure_, and _SendTaskHeartbeat_ are discussed. ### Simplifying your architecture with Amazon SNS and Amazon SQS In the [fourth episode][fourth-episode] of App 2025, viewers learn how to use [Amazon SNS][sns] topics and [Amazon SQS][sqs] queues to simplify the App 2025 architecture. The stability of SNS and SQS is demonstrated by a review of their long history. SNS topics and subscriber endpoints are introduced, as are SQS standard and FIFO queues. The "buffered fanout" pattern is presented to demonstrate how SNS, SQS, and EventBridge complement one another. ### Storing data with service integrations In the [fifth episode][fifth-episode] of App 2025, viewers learn how to use service integrations to store data persistently without invoking [AWS Lambda][lambda] functions. [Amazon Kinesis Data Firehose][firehose] is introduced as a serverless solution for ingesting streaming data and target for Amazon EventBridge rules. EventBridge input transformers are compared to Lambda functions as a mechanism for modifying data with Kinesis Data Firehose. The AWS Step Functions service integration with [Amazon DynamoDB][dynamodb] is discussed. Pass States are compared to Lambda functions as a mechanism for modifying data with Step Functions. An AWS SAM template is built that demonstrates how streaming data can be captured and stored for OLAP in [Amazon S3][s3] by Kinesis Data Firehose while concurrently being used for OLTP with Step Functions and DynamoDB – all serverlessly. ### Serverless reporting with Amazon Athena In the [sixth episode][sixth-episode] of App 2025, viewers learn how to analyze and report the data generated by their serverless applications. [Amazon Athena][athena] is introduced as a serverless solution for querying relational data stored in Amazon S3. The AWS Management Console is used to demonstrate how to access the data generated by Amazon Kinesis Data Firehose in the previous episode. Basic queries are written, including creating views. [Amazon QuickSight][quicksight] is introduced for visualizing the data stored in S3, including creating data sets and visualizations. The [Registry of Open Data on AWS][opendata] is explored to enable viewers to augment and enhance their applications with publicly available data sets hosted on AWS. Finally, the material covered in the session and the series is reviewed. ## Deploying the app 1. First, [deploy the infrastructure][deploy-infrastructure]. 1. Next, [deploy the customer microservice][deploy-customer]. 1. Next, [deploy the operations microservice][deploy-operations]. 1. Next, [deploy the billing microservice][deploy-billing]. 1. Finally, [deploy the simulator app][deploy-simulator]. ## Episode transcripts Machine-generated transcripts for each episode are available in the [transcripts](transcripts) directory. Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: MIT-0 [athena]: https://aws.amazon.com/athena/ [aws]: https://aws.amazon.com/ [callbacks]: https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#connect-wait-token [dynamodb]: https://aws.amazon.com/dynamodb/ [eventbridge]: https://aws.amazon.com/eventbridge/ [express-workflows]: https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-aws-step-functions-express-workflows/ [firehose]: https://aws.amazon.com/kinesis/data-firehose/ [iam]: https://aws.amazon.com/iam/ [lambda]: https://aws.amazon.com/lambda/ [opendata]: https://registry.opendata.aws/ [quicksight]: https://aws.amazon.com/quicksight/ [s3]: https://aws.amazon.com/s3/ [sam-cli]: https://github.com/awslabs/aws-sam-cli/ [serverless]: https://aws.amazon.com/serverless/ [sns]: https://aws.amazon.com/sns/ [sqs]: https://aws.amazon.com/sqs/ [standard-workflows]: https://docs.aws.amazon.com/step-functions/latest/dg/concepts-standard-vs-express.html [step-functions]: https://aws.amazon.com/step-functions/ [twitch]: https://twitch.tv/robsutter/ [first-episode]: https://youtu.be/jYmZH7j_MXA?t=80 [second-episode]: https://youtu.be/pdc6oorQ3lE [third-episode]: https://youtu.be/raFNW7KdehE [fourth-episode]: https://youtu.be/krBKiABQJAk [fifth-episode]: https://youtu.be/_nseply4SPc [sixth-episode]: https://youtu.be/XoQmbI_1MWg [deploy-infrastructure]: infrastructure/ [deploy-customer]: customer/ [deploy-operations]: operations/ [deploy-billing]: billing/ [deploy-simulator]: simulator/