AWS Lambda を使用してシンプルなカスタムサービスを作成する
注釈:本資料はAI技術を用いて翻訳されています。
概要
AccelByte Gaming Services (AGS) を拡張する方法の1つは、AccelByte Modular Extend SDK を使用して、カスタムビルドされたサービスから AGS エンドポイントを呼び出すことです。
AccelByte Extend SDK は複数のプログラミング言語で利用できます。このガイドでは、Go Extend SDK を使用して Go で記述された AWS Lambda Function URL を使用してシンプルなカスタムサービスを作成する方法を説明します。このシンプルなカスタムサービスは、ユーザーに統計を追加し、ユーザーの統計を取得し、ユーザーから統計を削除するエンドポイントを提供します。
目標
このガイドでは、以下の方法を説明します:
- AWS SAM CLI を使用して AWS Lambda プロジェクトを作成する
- AWS Lambda ハンドラーを作成する
- SAM テンプレート YAML を設定する
- AWS Lambda をローカルでテストする
- Lambda を AWS にデプロイする
前提条件
このガイドのタスクを実行するには、以下が必要です:
- AWS SAM CLI をインストール済み
- Docker をインストール済み
- Go 1.16 をインストール済み
- Postman をインストール済み
- AGS デモ環境へのアクセス権が付与されていること:
- Base URL:
https://prod.gamingservices.accelbyte.io - まだ持っていない場合は、ゲームネームスペースを作成してください。
Namespace IDを記録してください。 confidentialクライアントタイプで OAuth クライアントを作成してください。以下の権限を追加し、Client ID と Client Secret を記録してください:ADMIN:NAMESPACE:{namespace}:USER:*:STATITEM - CREATE, READ- 統計設定を作成し、
Stat Codeを記録してください。 - テストユーザーアカウントが1つ必要です。
User IDを記録してください。
- Base URL:
- AWS Lambda をデプロイするための
AWS_ACCESS_KEY_IDとAWS_SECRET_ACCESS_KEY
また、以下に精通している必要があります:
- Go での AWS Lambda Function URL の開発
- Go プロジェクトでの Go Extend SDK の使用
- AWS Lambda Function URL のデプロイ
AWS SAM CLI を使用して AWS Lambda プロジェクトを作成する
-
以下の
sam initコマンドを実行して、go1.xランタイムで新しい AWS Lambda プロジェクトを作成します。このプロジェクトにはhello-worldアプリテンプレートを使用します。sam init --name aws-lambda-example --runtime go1.x --app-template hello-world --no-tracing --no-application-insights -
sam initコマンドが成功すると、aws-lambda-exampleという名前の新しいフォルダが作成されます。
AWS Lambda ハンドラーを作成する
aws-lambda-example フォルダ内で、既存の hello-world フォルダを CreateUserStats、GetUserStats、DeleteUserStats という3つのフォルダにコピーします。これら3つの *UserStats フォルダのそれぞれで、main.go ファイルに必要なハンドラーを実装します。これらのハンドラーは、それぞれユーザーへの統計の追加、ユーザーの統計の取得、ユーザーからの統計の削除を行うためのものです。各フォルダで必要に応じて go mod tidy を実行してください。最後に、不要になった hello-world フォルダを削除します。
-
CreateUserStats/main.goにハンドラーを実装します。package main
import (
"encoding/json"
"fmt"
"strings"
"time"
iam "github.com/AccelByte/accelbyte-go-modular-sdk/iam-sdk/pkg"
"github.com/AccelByte/accelbyte-go-modular-sdk/services-api/pkg/utils/auth"
social "github.com/AccelByte/accelbyte-go-modular-sdk/social-sdk/pkg"
"github.com/AccelByte/accelbyte-go-modular-sdk/social-sdk/pkg/socialclient/user_statistic"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/sirupsen/logrus"
)
var (
// use the default config and token implementation
configRepo = *auth.DefaultConfigRepositoryImpl()
tokenRepo = *auth.DefaultTokenRepositoryImpl()
oAuth20Service = iam.OAuth20Service{
Client: iam.NewIamClient(&configRepo),
ConfigRepository: &configRepo,
TokenRepository: &tokenRepo,
}
userStatisticService = &social.UserStatisticService{
Client: social.NewSocialClient(&configRepo),
TokenRepository: &tokenRepo,
}
)
type Request struct {
Namespace string `json:"namespace"`
UserID string `json:"userId"`
StatCode string `json:"statCode"`
}
func main() {
lambda.Start(Handler)
}
func Handler(evt events.LambdaFunctionURLRequest) (events.LambdaFunctionURLResponse, error) {
// parse the events
request := Request{}
err := json.Unmarshal([]byte(evt.Body), &request)
if err != nil {
errString := fmt.Errorf("failed to parse the request. %s", err.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// parse the access token
reqToken := evt.Headers["authorization"]
splitToken := strings.Split(reqToken, "Bearer ")
if len(splitToken) == 1 || len(splitToken) > 2 {
errString := fmt.Errorf("invalid token. Token split \"Bearer\" and token authorization")
logrus.Print(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// login client
clientId := oAuth20Service.ConfigRepository.GetClientId()
clientSecret := oAuth20Service.ConfigRepository.GetClientSecret()
errLogin := oAuth20Service.LoginClient(&clientId, &clientSecret)
if errLogin != nil {
errString := fmt.Errorf("failed to login client. %s", errLogin.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// start token validation
errValidateToken := validateToken(splitToken[1], request.Namespace, request.UserID)
if errValidateToken != nil {
errString := fmt.Errorf("failed to validate token. %s", errValidateToken.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// create user stat item
inputUserStatItem := &user_statistic.CreateUserStatItemParams{
Namespace: request.Namespace,
StatCode: request.StatCode,
UserID: request.UserID,
}
errUserStatItem := userStatisticService.CreateUserStatItemShort(inputUserStatItem)
if errUserStatItem != nil {
errString := fmt.Errorf("failed to create user stat item. %s", errUserStatItem.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
return events.LambdaFunctionURLResponse{
StatusCode: 200,
Body: "User stat code added successfully",
}, nil
}
func validateToken(accessToken, namespace, userId string) error {
// initialize token validator
tokenValidator := iam.NewTokenValidator(oAuth20Service, time.Hour)
tokenValidator.Initialize()
// validate stat item
requiredPermissionStatItem := iam.Permission{
Action: 1, // create
Resource: fmt.Sprintf("ADMIN:NAMESPACE:%s:USER:%s:STATITEM", namespace, userId),
}
errValidateStatItem := tokenValidator.Validate(accessToken, &requiredPermissionStatItem, &namespace, nil)
if errValidateStatItem != nil {
return errValidateStatItem
}
return nil
} -
GetUserStats/main.goにハンドラーを実装します。package main
import (
"encoding/json"
"fmt"
"strings"
"time"
iam "github.com/AccelByte/accelbyte-go-modular-sdk/iam-sdk/pkg"
"github.com/AccelByte/accelbyte-go-modular-sdk/services-api/pkg/utils/auth"
social "github.com/AccelByte/accelbyte-go-modular-sdk/social-sdk/pkg"
"github.com/AccelByte/accelbyte-go-modular-sdk/social-sdk/pkg/socialclient/user_statistic"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/sirupsen/logrus"
)
var (
// use the default config and token implementation
configRepo = *auth.DefaultConfigRepositoryImpl()
tokenRepo = *auth.DefaultTokenRepositoryImpl()
oAuth20Service = iam.OAuth20Service{
Client: iam.NewIamClient(&configRepo),
ConfigRepository: &configRepo,
TokenRepository: &tokenRepo,
}
userStatisticService = &social.UserStatisticService{
Client: social.NewSocialClient(&configRepo),
TokenRepository: &tokenRepo,
}
)
type Request struct {
Namespace string `json:"namespace"`
UserID string `json:"userId"`
StatCode string `json:"statCode"`
Limit int `json:"limit"`
Offset int `json:"offset"`
Tags string `json:"tags"`
}
func main() {
lambda.Start(Handler)
}
func Handler(evt events.LambdaFunctionURLRequest) (events.LambdaFunctionURLResponse, error) {
// parse the events body
request := Request{}
err := json.Unmarshal([]byte(evt.Body), &request)
if err != nil {
errString := fmt.Errorf("failed to parse the request. %s", err.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// parse the access token
reqToken := evt.Headers["authorization"]
splitToken := strings.Split(reqToken, "Bearer ")
if len(splitToken) == 1 || len(splitToken) > 2 {
errString := fmt.Errorf("invalid token. Token split \"Bearer\" and token authorization")
logrus.Print(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// login client
clientId := oAuth20Service.ConfigRepository.GetClientId()
clientSecret := oAuth20Service.ConfigRepository.GetClientSecret()
errLogin := oAuth20Service.LoginClient(&clientId, &clientSecret)
if errLogin != nil {
errString := fmt.Errorf("failed to login client. %s", errLogin.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// start token validation
errValidateToken := validateToken(splitToken[1], request.Namespace, request.UserID)
if errValidateToken != nil {
errString := fmt.Errorf("failed to validate token. %s", errValidateToken.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// get user stat item
limit := int32(request.Limit)
offset := int32(request.Offset)
inputUserStatItem := &user_statistic.GetUserStatItemsParams{
Limit: &limit,
Namespace: request.Namespace,
Offset: &offset,
StatCodes: &request.StatCode,
Tags: &request.Tags,
UserID: request.UserID,
}
getUserStatItem, errUserStatItem := userStatisticService.GetUserStatItemsShort(inputUserStatItem)
if errUserStatItem != nil {
errString := fmt.Errorf("failed to create user stat item. %s", errUserStatItem.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
var js []byte
js, err = json.Marshal(getUserStatItem)
if err != nil {
errString := fmt.Errorf("failed to marshal the response. %s", err.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
return events.LambdaFunctionURLResponse{
StatusCode: 200,
Body: string(js),
}, nil
}
func validateToken(accessToken, namespace, userId string) error {
// initialize token validator
tokenValidator := iam.NewTokenValidator(oAuth20Service, time.Hour)
tokenValidator.Initialize()
// validate stat item
requiredPermissionStatItem := iam.Permission{
Action: 2, // read
Resource: fmt.Sprintf("ADMIN:NAMESPACE:%s:USER:%s:STATITEM", namespace, userId),
}
errValidateStatItem := tokenValidator.Validate(accessToken, &requiredPermissionStatItem, &namespace, nil)
if errValidateStatItem != nil {
return errValidateStatItem
}
return nil
} -
DeleteUserStats/main.goにハンドラーを実装します。package main
import (
"encoding/json"
"fmt"
"strings"
"time"
iam "github.com/AccelByte/accelbyte-go-modular-sdk/iam-sdk/pkg"
"github.com/AccelByte/accelbyte-go-modular-sdk/services-api/pkg/utils/auth"
social "github.com/AccelByte/accelbyte-go-modular-sdk/social-sdk/pkg"
"github.com/AccelByte/accelbyte-go-modular-sdk/social-sdk/pkg/socialclient/user_statistic"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/sirupsen/logrus"
)
var (
// use the default config and token implementation
configRepo = *auth.DefaultConfigRepositoryImpl()
tokenRepo = *auth.DefaultTokenRepositoryImpl()
oAuth20Service = iam.OAuth20Service{
Client: iam.NewIamClient(&configRepo),
ConfigRepository: &configRepo,
TokenRepository: &tokenRepo,
}
userStatisticService = &social.UserStatisticService{
Client: social.NewSocialClient(&configRepo),
TokenRepository: &tokenRepo,
}
)
type Request struct {
Namespace string `json:"namespace"`
UserID string `json:"userId"`
StatCode string `json:"statCode"`
}
func main() {
lambda.Start(Handler)
}
func Handler(evt events.LambdaFunctionURLRequest) (events.LambdaFunctionURLResponse, error) {
// parse the events
request := Request{}
err := json.Unmarshal([]byte(evt.Body), &request)
if err != nil {
errString := fmt.Errorf("failed to parse the request. %s", err.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// parse the access token
reqToken := evt.Headers["authorization"]
splitToken := strings.Split(reqToken, "Bearer ")
if len(splitToken) == 1 || len(splitToken) > 2 {
errString := fmt.Errorf("invalid token. Token split \"Bearer\" and token authorization")
logrus.Print(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// login client
clientId := oAuth20Service.ConfigRepository.GetClientId()
clientSecret := oAuth20Service.ConfigRepository.GetClientSecret()
errLogin := oAuth20Service.LoginClient(&clientId, &clientSecret)
if errLogin != nil {
errString := fmt.Errorf("failed to login client. %s", errLogin.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// start token validation
errValidateToken := validateToken(splitToken[1], request.Namespace, request.UserID)
if errValidateToken != nil {
errString := fmt.Errorf("failed to validate token. %s", errValidateToken.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
// delete user stat item
inputDeleteUserStatItem := &user_statistic.DeleteUserStatItemsParams{
Namespace: request.Namespace,
StatCode: request.StatCode,
UserID: request.UserID,
}
errDeleteUserStatItems := userStatisticService.DeleteUserStatItems(inputDeleteUserStatItem)
if errDeleteUserStatItems != nil {
errString := fmt.Errorf("failed to delete user stat code. %s", errDeleteUserStatItems.Error())
logrus.Error(errString)
return events.LambdaFunctionURLResponse{}, errString
}
return events.LambdaFunctionURLResponse{
StatusCode: 200,
Body: "User stat code deleted successfully",
}, nil
}
func validateToken(accessToken, namespace, userId string) error {
// initialize token validator
tokenValidator := iam.NewTokenValidator(oAuth20Service, time.Hour)
tokenValidator.Initialize()
// validate stat item
requiredPermissionStatItem := iam.Permission{
Action: 8, // delete
Resource: fmt.Sprintf("ADMIN:NAMESPACE:%s:USER:%s:STATITEM", namespace, userId),
}
errValidateStatItem := tokenValidator.Validate(accessToken, &requiredPermissionStatItem, &namespace, nil)
if errValidateStatItem != nil {
errString := fmt.Errorf("failed to validate permission for stat item. %s", errValidateStatItem)
logrus.Error(errString)
return errString
}
return nil
}
SAM テンプレート YAML を設定する
このステップでは、AWS SAM の template.yaml で2つのことを行います: 必要な環境変数を設定し、3つの UserStats を接続します。
-
必要な環境変数を設定します:
AB_BASE_URL、AB_CLIENT_ID、AB_CLIENT_SECRET -
AWS Lambda Function URL として作成された3つの
UserStatsハンドラーを接続します:template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
aws-lambda-example
AccelByte Go Extend SDK Lambda Example
Globals:
Function:
Timeout: 15
Environment:
Variables:
AB_BASE_URL: https://prod.gamingservices.accelbyte.io
AB_CLIENT_ID: <Put your AccelByte Client ID here>
AB_CLIENT_SECRET: <Put your AccelByte Client Secret here>
Resources:
CreateUserStatsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: CreateUserStats
Handler: main
Runtime: go1.x
CreateUserStatsFunctionUrl:
Type: AWS::Lambda::Url
Properties:
AuthType: NONE
TargetFunctionArn:
Ref: CreateUserStatsFunction
DeleteUserStatsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: DeleteUserStats
Handler: main
Runtime: go1.x
DeleteUserStatsFunctionUrl:
Type: AWS::Lambda::Url
Properties:
AuthType: NONE
TargetFunctionArn:
Ref: DeleteUserStatsFunction
GetUserStatsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: GetUserStats
Handler: main
Runtime: go1.x
GetUserStatsFunctionUrl:
Type: AWS::Lambda::Url
Properties:
AuthType: NONE
TargetFunctionArn:
Ref: GetUserStatsFunction
Outputs:
CreateUserStatsFunction:
Description: "Create User Stats Function ARN"
Value: !GetAtt CreateUserStatsFunction.Arn
CreateUserStatsFunctionUrlEndpoint:
Description: "Access CreateUserStats function with this URL"
Value: !GetAtt CreateUserStatsFunctionUrl.FunctionUrl
CreateUserStatsFunctionIamRole:
Description: "Implicit IAM Role created for Create User Stats Function"
Value: !GetAtt CreateUserStatsFunctionRole.Arn
DeleteUserStatsFunction:
Description: "Delete User Stats Function ARN"
Value: !GetAtt DeleteUserStatsFunction.Arn
DeleteUserStatsFunctionUrlEndpoint:
Description: "Access DeleteUserStats function with this URL"
Value: !GetAtt DeleteUserStatsFunctionUrl.FunctionUrl
DeleteUserStatsFunctionIamRole:
Description: "Implicit IAM Role created for Delete User Stats Function"
Value: !GetAtt DeleteUserStatsFunctionRole.Arn
GetUserStatsFunction:
Description: "Get User Stats Function ARN"
Value: !GetAtt GetUserStatsFunction.Arn
GetUserStatsFunctionUrlEndpoint:
Description: "Access GetUserStats function with this URL"
Value: !GetAtt GetUserStatsFunctionUrl.FunctionUrl
GetUserStatsFunctionIamRole:
Description: "Implicit IAM Role created for Get User Stats Function"
Value: !GetAtt GetUserStatsFunctionRole.Arn
AWS Lambda をローカルでテストする
-
AWS Lambda をビルドし、テスト用にローカルで提供します。
sam build
sam local start-lambda -
以下のコマンドを使用して、ユーザーへの統計の追加、ユーザーの統計の取得、ユーザーからの統計の削除を試してみます。
# Set the required environment variables
AB_BASE_URL=https://prod.gamingservices.accelbyte.io
AB_CLIENT_ID='xxxxxxxxxx' # Your Client ID
AB_CLIENT_SECRET='xxxxxxxxxx' # Your Client Secret
AB_NAMESPACE='xxxxxxxxxx' # Your Namespace ID
TEST_USER_ID='xxxxxxxxx' # Your test User ID
TEST_STAT_CODE='xxxxxxxxxx' # Your test Stat Code
# Login client
ACCESS_TOKEN="$(curl -s ${AB_BASE_URL}/iam/v3/oauth/token -H 'Content-Type: application/x-www-form-urlencoded' -u "$AB_CLIENT_ID:$AB_CLIENT_SECRET" -d "grant_type=client_credentials" | jq --raw-output .access_token)"
# Add a stat to a user
curl -X POST "http://127.0.0.1:3001/2015-03-31/functions/CreateUserStatsFunction/invocations" -d "{\"headers\":{\"authorization\":\"Bearer $ACCESS_TOKEN\"},\"body\":\"{\\\"namespace\\\":\\\"$AB_NAMESPACE\\\",\\\"userId\\\":\\\"$TEST_USER_ID\\\",\\\"statCode\\\":\\\"$TEST_STAT_CODE\\\"}\"}"
# Get stats of a user
curl -X POST "http://127.0.0.1:3001/2015-03-31/functions/GetUserStatsFunction/invocations" -d "{\"headers\":{\"authorization\":\"Bearer $ACCESS_TOKEN\"},\"body\":\"{\\\"namespace\\\":\\\"$AB_NAMESPACE\\\",\\\"userId\\\":\\\"$TEST_USER_ID\\\",\\\"statCode\\\":\\\"$TEST_STAT_CODE\\\"}\"}"
# Delete a stat from a user
curl -X POST "http://127.0.0.1:3001/2015-03-31/functions/DeleteUserStatsFunction/invocations" -d "{\"headers\":{\"authorization\":\"Bearer $ACCESS_TOKEN\"},\"body\":\"{\\\"namespace\\\":\\\"$AB_NAMESPACE\\\",\\\"userId\\\":\\\"$TEST_USER_ID\\\",\\\"statCode\\\":\\\"$TEST_STAT_CODE\\\"}\"}"
AWS にデプロイする
-
AWS Lambda Function URL をビルドしてデプロイします。デプロイされた URL (例:
https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.us-west-2.on.aws/) をメモしてください。CreateUserStats、GetUserStats、DeleteUserStatsそれぞれに対して3つの URL があります。sam build
sam deploy --guided -
この Postman コレクションをインポートして、Postman を使用してユーザーへの統計の追加、ユーザーの統計の取得、ユーザーからの統計の削除を試してみます。
-
インポートした Postman コレクションで以下の変数を設定します。
base_url='https://prod.gamingservices.accelbyte.io'
client_id='xxxxxxxxxx' # Your Client ID
client_secret='xxxxxxxxxx' # Your Client Secret
create_user_stats_url='https://xxxxxxxxxx.lambda-url.us-west-2.on.aws/' # Your create user stats URL
get_user_stats_url='https://xxxxxxxxxx.lambda-url.us-west-2.on.aws/' # Your get user stats URL
delete_user_stats_url='https://xxxxxxxxxx.lambda-url.us-west-2.on.aws/' # Your delete user stats URL -
00 GetAccessTokenを実行し、後続のリクエストのためにaccess tokenを記録します。 -
01 CreateUserStatsを開き、AuthorizationのBearer Tokenにaccess tokenを設定します。次に、以下のようにリクエストボディを変更し、実行してユーザーに統計を追加します。{
"namespace":"Put Your Namespace ID",
"userId":"Put Your Test User Id",
"statCode":"Put Your Test Stat Code"
} -
02 GetUserStatsを開き、AuthorizationのBearer Tokenにaccess tokenを設定し、以下のようにリクエストボディを変更して実行し、ユーザーの統計を取得します。追加した統計がリストに表示されるはずです。{
"namespace":"Put Your Namespace ID",
"userId":"Put Your Test User Id",
"statCode":"Put Your Test Stat Code"
} -
03 DeleteUserStatsを開き、AuthorizationのBearer Tokenにaccess tokenを設定します。次に、以下のようにリクエストボディを変更し、実行してユーザーの統計を削除します。この後に再度02 GetUserStatsを試すと、統計がリストに表示されなくなっているはずです。{
"namespace":"Put Your Namespace ID",
"userId":"Put Your Test User Id",
"statCode":"Put Your Test Stat Code"
}
次のステップ
次のステップとして、他の AGS エンドポイントを呼び出す別のカスタムサービスを作成してみることができます。
リソース
- AWS Lambda Example Update で完全なソースコードを参照してください。
- Go Extend SDK を使用して一般的なユースケースを実現する方法の例については、一般的なユースケースリストを参照してください。
- 呼び出す必要がある AGS エンドポイントがわかっていて、Go Modular Extend SDK を使用して呼び出したい場合は、Operations ドキュメントを参照してください。