# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > (SO9046) Multiplayer Session-based Game hosting on AWS v1.0.0. This solution shows how to host multiplayer games on AWS leveraging a serverless backend and Amazon GameLift. # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 100 Resources: # Table for player data GameLiftExamplePlayerData: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: ID AttributeType: S KeySchema: - AttributeName: ID KeyType: HASH BillingMode: PAY_PER_REQUEST TableName: GameLiftExamplePlayerData # Table for Matchmaking events data GameLiftExampleMatchmakingTickets: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: TicketID AttributeType: S KeySchema: - AttributeName: TicketID KeyType: HASH BillingMode: PAY_PER_REQUEST TableName: GameLiftExampleMatchmakingTickets # Using a TTL attribute to remove tickets after one day TimeToLiveSpecification: AttributeName: TTL Enabled: True # SNS Topic for Matchmaking events MatchmakingEventsSNSTopic: Type: AWS::SNS::Topic Properties: TopicName: "GameLiftExampleMatchmakingEventsSNSTopic" GameLiftAccessSNSPolicy: Type: AWS::SNS::TopicPolicy Properties: PolicyDocument: Id: MyTopicPolicy Version: '2012-10-17' Statement: - Sid: My-statement-id Effect: Allow Principal: Service: gamelift.amazonaws.com Action: sns:Publish Resource: !Ref MatchmakingEventsSNSTopic Topics: - !Ref MatchmakingEventsSNSTopic GameServiceAPI: Type: AWS::Serverless::Api Properties: StageName: Prod # Authenticate users with IAM (Cognito identities) Auth: DefaultAuthorizer: AWS_IAM InvokeRole: NONE #Using the Lambda role instead of caller GameLiftAccessPolicy: Properties: Description: Policy for full gamelift access Path: / PolicyDocument: Statement: - Action: gamelift:* Effect: Allow Resource: '*' Version: '2012-10-17' Type: AWS::IAM::ManagedPolicy DynamoDBAccessPolicy: Properties: Description: Policy for dynamodb access Path: / PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'dynamodb:DeleteItem' - 'dynamodb:GetItem' - 'dynamodb:PutItem' - 'dynamodb:UpdateItem' Resource: 'Fn::Join': - '' - - 'arn:aws:dynamodb:' - Ref: 'AWS::Region' - ':' - Ref: 'AWS::AccountId' - ':table/GameLiftExamplePlayerData' - Effect: Allow Action: - 'dynamodb:GetItem' - 'dynamodb:PutItem' - 'dynamodb:UpdateItem' Resource: 'Fn::Join': - '' - - 'arn:aws:dynamodb:' - Ref: 'AWS::Region' - ':' - Ref: 'AWS::AccountId' - ':table/GameLiftExampleMatchmakingTickets' Type: AWS::IAM::ManagedPolicy RequestMatchmakingFunction: Type: AWS::Serverless::Function Properties: CodeUri: gameservice/ Handler: requestmatchmaking.requestMatchMaking Runtime: nodejs16.x Policies: - !Ref GameLiftAccessPolicy - !Ref DynamoDBAccessPolicy Events: HelloWorld: Type: Api Properties: RestApiId: !Ref GameServiceAPI Path: /requestmatchmaking Method: get RequestMatchStatusFunction: 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: gameservice/ Handler: requestmatchstatus.requestMatchStatus Runtime: nodejs16.x Policies: - !Ref GameLiftAccessPolicy - !Ref DynamoDBAccessPolicy Events: HelloWorld: Type: Api Properties: RestApiId: !Ref GameServiceAPI Path: /requestmatchstatus Method: get # Processes the messages sent by GameLift FlexMatch to the topic provided ProcessMatchmakingEvents: Type: AWS::Serverless::Function Properties: CodeUri: gameservice/ Handler: processmatchmakingevents.handler Runtime: nodejs16.x Policies: - !Ref DynamoDBAccessPolicy Events: SNSEvent: Type: SNS Properties: Topic: !Ref MatchmakingEventsSNSTopic # CloudFormation Dashboard for backend metrics DashboardBackendMetrics: Type: AWS::CloudWatch::Dashboard Properties: DashboardName: GameLift-Game-Backend-Metrics DashboardBody: Fn::Sub: |- {"widgets":[ {"type": "text","x": 0, "y": 0,"width": 24,"height": 1,"properties": {"markdown": "# AWS Lambda & API Gateway"}}, {"type": "metric", "x": 0,"y": 1,"width": 8,"height": 6,"properties": {"metrics": [[ "AWS/ApiGateway", "Count", "ApiName", "GameLiftExampleServerlessGameBackend" ],[ ".", "5XXError", ".", "." ],[ ".", "4XXError", ".", "." ]],"region": "${AWS::Region}","title": "API Gateway Requests","period": 60,"view": "timeSeries","stacked": false,"stat": "Sum"}}, {"type": "metric","x": 8,"y": 1,"width": 8,"height": 6,"properties": {"metrics": [[ "AWS/Lambda", "Invocations", "FunctionName", "${ProcessMatchmakingEvents}", { "stat": "Sum", "label": "ProcessMatchmakingEvents" } ],[ "...", "${RequestMatchStatusFunction}", { "stat": "Sum", "label": "RequestMatchStatusFunction" } ],[ "...", "${RequestMatchmakingFunction}", { "stat": "Sum", "label": "RequestMatchmakingFunction" } ],[ { "expression": "SUM(METRICS())", "label": "Total Invocations", "yAxis": "right" } ]],"region": "${AWS::Region}","title": "Lambda Invocations"}}, {"type": "metric","x": 16, "y": 1,"width": 8,"height": 6,"properties": {"metrics": [[ "AWS/Lambda", "Errors", "FunctionName", "${ProcessMatchmakingEvents}", { "stat": "Sum", "label": "ProcessMatchmakingEvents" } ],[ "...", "${RequestMatchStatusFunction}", { "stat": "Sum", "label": "RequestMatchStatusFunction" } ],[ "...", "${RequestMatchmakingFunction}", { "stat": "Sum", "label": "RequestMatchmakingFunction" } ],[ { "expression": "SUM(METRICS())", "label": "Total Errors", "yAxis": "right" } ]],"region": "${AWS::Region}","title": "Lambda Errors"}}, {"type": "metric","x": 0,"y": 7,"width": 8,"height": 6,"properties": {"metrics": [[ "AWS/Lambda", "Duration", "FunctionName", "${ProcessMatchmakingEvents}", { "stat": "Average", "label": "ProcessMatchmakingEvents" } ],[ "...", "${RequestMatchStatusFunction}", { "stat": "Average", "label": "RequestMatchStatusFunction" } ],[ "...", "${RequestMatchmakingFunction}", { "stat": "Average", "label": "RequestMatchmakingFunction" } ],[ { "expression": "AVG(METRICS())", "label": "Average Duration", "yAxis": "right" } ]],"region": "${AWS::Region}","title": "Lambda Duration (average)"}}, {"type": "metric","x": 8,"y": 7,"width": 8,"height": 6,"properties": {"metrics": [[ "AWS/Lambda", "ConcurrentExecutions", "FunctionName", "${ProcessMatchmakingEvents}", { "stat": "Maximum", "label": "ProcessMatchmakingEvents" } ],[ "...", "${RequestMatchStatusFunction}", { "stat": "Maximum", "label": "RequestMatchStatusFunction" } ],[ "...", "${RequestMatchmakingFunction}", { "stat": "Maximum", "label": "RequestMatchmakingFunction" } ],[ { "expression": "SUM(METRICS())", "label": "Total ConcurrentExecutions", "yAxis": "right" } ]],"region": "${AWS::Region}","title": "Lambda ConcurrentExecutions"}}, {"type": "text","x": 0, "y": 8,"width": 24,"height": 1,"properties": {"markdown": "# Amazon DynamoDB"}}, {"type": "metric","x": 0,"y": 9,"width": 8,"height": 6,"properties": {"metrics": [[ "AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "GameLiftExampleMatchmakingTickets", "Operation", "Scan" ],[ "...", "Query" ],[ "...", "PutItem" ],[ "...", "GetItem" ]],"region": "${AWS::Region}","title": "MatchmakingTickets Table Latency","period": 60,"view": "timeSeries","stacked": false}}, {"type": "metric","x": 8,"y": 9,"width": 8,"height": 6,"properties": {"metrics": [[ "AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "GameLiftExamplePlayerData", "Operation", "Scan" ],[ "...", "Query" ],[ "...", "PutItem" ],[ "...", "GetItem" ]],"region": "${AWS::Region}","title": "PlayerData Table Latency","period": 60,"view": "timeSeries","stacked": false}} ]} Outputs: # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function # Find out more about other implicit resources you can reference within SAM # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api GameServiceAPI: Description: "API Gateway endpoint URL for Prod stage for Game Service API" Value: !Sub "https://${GameServiceAPI}.execute-api.${AWS::Region}.amazonaws.com/Prod/" MatchmakingSNSTopic: Description: "Matchmaking SNS Topic Arn" Value: !Ref MatchmakingEventsSNSTopic Export: Name: !Sub "${AWS::StackName}-MatchmakingEventsSNSTopic" GameServiceApiArn: Description: "The Execute ARN for the Cognito Role Permissions" Value: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${GameServiceAPI}/*/*/*" Export: Name: !Sub ${AWS::StackName}:GameServiceApiArn