GithubHelp home page GithubHelp logo

zph / serverless-rps-lab Goto Github PK

View Code? Open in Web Editor NEW

This project forked from kobmic/serverless-rps-lab

0.0 1.0 0.0 585 KB

lab: serverless rps with AWS Lambda, API Gateway

Clojure 35.10% Java 34.68% Shell 30.21%

serverless-rps-lab's Introduction

Serverless rock-paper-scissors lab using AWS Lambda & API Gateway

Prereqs

  • AWS account
  • Java 8
  • gradle

Use repository

git clone [email protected]:kobmic/serverless-rps-lab.git
cd serverless-rps-lab

If you get stuck you can check the branch solution.

0. Naming

When doing this lab in teams, to avoid name conflicts choose a team name and use it when naming lambda functions etc, i.e. "awesomeTeam-helloWorld" instead of "helloWorld". In orer to use the automatic test script described below also make sure that you follow a naming convention for all of your lambda methods. Use a prefix, such as "awesomeTeam-" and then name your methods such as "awesomeTeam-getGames", "awesomeTeam-makeMove" and so on.

1. Hello AWS Lambda

You can skip this step if you're already familiar with AWS lamba. In this step you will use a simple Java handler to create a Lambda function.

cd hello-lambda

AWS Lambda functions in Java

  • must be stateless to enable scaling
  • expect local file system access, child processes, and similar artifacts to be limited to the lifetime of the request
  • AWS Lambda provides 2 libraries
    • aws-lambda-java-core: provides the Context object, RequestStreamHandler, and the RequestHandler interfaces
    • aws-lambda-java-events: predefined types that you can use when writing Lambda functions to process events published by i.e. Amazon S3, Amazon Kinesis, Amazon SNS

Add dependencies

Add compile dependencies to your build.gradle:

'com.amazonaws:aws-lambda-java-core:1.0.0',
'com.amazonaws:aws-lambda-java-events:1.0.0' 

Build

gradle build

Implement a function handler

Implement a simple function handler in your HelloLambda class. Lambda supports two approaches for creating a handler:

  • Loading handler method directly without having to implement an interface
  • Implementing standard interfaces RequestStreamHandler or RequestHandler

First parameter of the handler function is input, second parameter the context. Supported input/output types for a handler are: simple Java types (String, Integer, Boolean, Map, and List types) or POJO or Stream type. AWS Lambda serializes based on standard bean naming conventions (use mutable POJOs with public getters and setters).

To learn more about function handlers see docs.

public String helloWorld(String input, Context context) {
	LambdaLogger logger = context.getLogger();
    logger.log("received : " + input);
    return String.format("Hello %s.", input);
}

Invocation types

  • RequestResponse invocation type: synchronous, used i.e when testing from AWS Lambda console
  • Event invocation type: asynchronous, used with event sources such as Amazon S3, Amazon Kinesis, and Amazon SNS

Create a deployment package

Now you package and upload your code to create your Lambda function. You will specify the com.jayway.rps.HelloLambda::helloWorld method reference as the handler.Your deployment package can be a .zip file or a standalone .jar. The gradle project contains a task for creating a zip:

gradle buildZip

Create Lambda Function

  • Login to the AWS Lambda console.
  • Choose Create a Lambda function
  • In step 1 Select blueprint, choose the hello-world blueprint.
  • In step 2 Configure function specify Java runtime, your handler, upload your zip, and select or create execution role (see below)
  • In step 3 Create function

Execution and invocation permissions

You must grant permissions for your Lambda function to access AWS resources like S3, DynamoDB or others. These are granted via an IAM role, called execution role. The entity invoking your Lambda function must have permission to do so. I.e. S3 or API Gateway needs permission to invoke your lambda function. See docs

Test

Configure a sample event in the console and test your lambda function.

Troubleshooting and monitoring

AWS Lambda automatically monitors Lambda functions, reporting metrics through Amazon CloudWatch. You can insert logging statements into your code that will be pushed to a CloudWatch Logs group associated with your Lambda function (Lambda/). In Java use the LambdaLogger:

LambdaLogger logger = context.getLogger();
logger.log("some useful log");

2. Create a lambda function that consumes JSON

Write a new lambda function createGame that consumes and produces JSON. Upload and test.

example JSON in:

{"name": "Player1", "email": "[email protected]" }

example JSON out:

{"gameid": "unique-gameid-could-be-uuid"}

3. Hello API Gateway

You can skip this step if you're already familiar with AWS API Gateway. In this step, you will see how to use API Gateway to create a custom API, connect your custom API to a AWS Lambda function, and then call the Lambda function from a client through API Gateway.

Basic Concepts

  • REST API defined as set of resources and methods
  • HTTP(s) endpoints for Lambda functions and other AWS Services
  • For every resource specify one or more methods to invoke it
  • Integration types:
    • Lambda Function
    • HTTP Proxy
    • Mock Integration
    • AWS Sevice Proxy

To learn more about API Gateway see docs

Create API

  • in AWS API Gateway console Create API
  • add resource games to your API
  • create method POST for this resource, choose integration type Lambda Function and select region and function createGame

Models and Mapping

In API Gateway, you use models and mapping templatesto transform data from one schema to another. API Gateway uses the Velocity Template Language (VTL) and JSONPath expressions to define mapping templates. To learn more about models and mapping see docs

Test

Test your endpoint with curl, i.e.

curl -X POST -H "ContentType: application/json"
 -d '{"name":"player1","email": "[email protected]"}' 
 https://0fjidtcksb.execute-api.eu-west-1.amazonaws.com/rpsDevStage/games

4. Serverless Rock-Paper-Scissors

In this step you will implement the rock-paper-scissors game using API Gateway & Lambda functions. You will use Amazon Dynamo DB to store your data in table rpslab-games in region eu-west1. Specify a hash key on gameId. Make sure to add permissions to the execution role you used for your lambda function, i.e.

{
	"Effect": "Allow",
    "Action": "dynamodb:*",
    "Resource": "arn:aws:dynamodb:eu-west-1:554360467205:table/rpslab-games"
} 

Architecture

Architecture

Game states

game states

Create Game

  • Write a lambda function that creates a new game, use com.jayway.rps.infra.GameStore utility class to store the new game in DynamoDB. You'll find a project template in directory serverless-rps.

Json in:

{
	"email": "[email protected]"
}

Json out:

{
	"gameId": "a7f7615c-c385-457c-93a5-1267dfe8787e",
	"state": "created",
	"player1": "[email protected]"
}
  • reuse your API Gateway from above, or create a new one, as before you'll need a games resource with method POST that will use your lambda function createGame
  • test your API and make sure the new game is persisted in DynamoDB table rpslab-games
  • change message repsonse from 200 to 201

Get Game

  • Implement GetGameLambda

Json in:

{
	"gameId": "c89dc950-141e-46ea-9f99-b1f54fb9c46d",
}

Json out:

{
	"gameId": "c89dc950-141e-46ea-9f99-b1f54fb9c46d",
	"state": "ended",
	"player1": "[email protected]",
	"player2": "[email protected]",
	"player1Move": "rock",
	"player2Move": "rock",
	"winner": "tie""
}	
  • add a new resource gameid under the games resource. Path should be {gameid}
  • add method GET to resource games/{gameid}
  • add a mapping to map the path parameter {gameid} to Json
    • in resource view select your new GET method
    • choose integration request
    • add mapping template 'application/json'
    • add template (change from "Input passthrough"): { "gameId" : "$input.params('gameid')" }

Join Game

  • Implement JoinGameLambda

Json in:

{
	"gameId": "c89dc950-141e-46ea-9f99-b1f54fb9c46d",
	"email": "[email protected]"
}

Json out:

{
	"gameId": "c89dc950-141e-46ea-9f99-b1f54fb9c46d",
	"state": "ready",
	"player1": "[email protected]",
	"player2": "[email protected]"
}	
  • add resource games/{gameid} with method PUT

Mapping for {gameid} and add back remaining JSON properties in request body:

#set($inputRoot = $input.path('$'))
{ 
	"gameId" : "$input.params('gameid')",
	"email": "$inputRoot.email"
}

Make Move

  • Implement MakeMoveLambda

Json in:

{
	"gameId": "c89dc950-141e-46ea-9f99-b1f54fb9c46d",
	"email": "[email protected]",
	"move": "rock"
}

Json out:

{
	"gameId": "c89dc950-141e-46ea-9f99-b1f54fb9c46d",
	"state": "waiting",
	"player1": "[email protected]",
	"player2": "[email protected]",
	"player1Move": "rock",
}	
  • add method POST to resource games/{gameid}

Mapping for {gameid} and add back remaining JSON properties in request body:

#set($inputRoot = $input.path('$'))
{ 
	"gameId" : "$input.params('gameid')",
	"email": "$inputRoot.email",
	"move": "$inputRoot.move"
}

Get Games

  • Implement GetGamesLambda

Json in:

{
	"state": "created"
}

Json out:

[
  {
	"gameId": "a6e1efd2-952d-4ea2-982a-663a12ac3a24",
    "state": "created",
	"player1": "[email protected]"
  },
  {
	"gameId": "b5c9be16-5633-40ae-950c-a8b33a171d48",
	"state": "created",
	"player1": "[email protected]"
  }
]	
  • add method GET to resource games
  • use a query parameter state to query games by state, i.e. /games?state=ended
  • add the query parameter in "Method request" step

Mapping for query parameter state:

{ 
	"state" : "$input.params('state')"
}

Test

Test your API with curl, or use Ulriks script:

Usage

$ cd serverless-rps
$ etc/test-api.sh --help
Usage: test-api.sh --profile=<profile> --prefix=<prefix>
        --api-id=<api id> --stage=<stage>
        [--test={api|lambda}] [--region=<region>]
        [--player1=<email>] [--player2=<email>]
        [--camelCase] [--debug] [--help]

where:
  profile     aws-cli profile, eg 'jayway-devops-mike'
  prefix      lambda function prefix, eg 'mike' if lambda is 'mike-get-game'
  api-id      id of the Amazon API Gateway API, eg '5ikia5f4v9'
  stage       name of API Gateway stage, eg 'mike_rps'
  test        what to test, {api|lambda} (default: api)
  region      name of AWS region (default: eu-west-1)
  player1     email of player1 (default: [email protected])
  player2     email of player2 (default: [email protected])
  camel-case  expect 'mike-createGame' lambda names (default: 'mike-create-game')

If your Lambda functions are not dash-separated lower-case prefixed names,
like 'mike-create-game', 'mike-join-game', 'mike-make-move', 'mike-get-game',
and 'mike-get-games', but rather 'mike-createGame', 'mike-joinGame',
'mike-makeMove', 'mike-getGame', and 'mike-getGames', then you must use the
flag '--camel-case'. If they have a naming-convention different than those two,
you're on your own.

Lambda

Use the parameter --test=lambda to test all the Lambda functions:

$ etc/test-api.sh --test=lambda --profile=jayway-devops-ulrik --prefix=ulsa --api-id=5ikia5f4v9 --stage=ulsa_rps
Testing the AWS Lambda functions
...

API Gateway

Use the parameter --test=api to test all the API resources and methods:

$ etc/test-api.sh --test=api --profile=jayway-devops-ulrik --prefix=ulsa --api-id=5ikia5f4v9 --stage=ulsa_rps

Testing the Amazon API Gateway API on https://5ikia5f4v9.execute-api.eu-west-1.amazonaws.com/ulsa_rps/games
Result files will be located in /var/folders/gc/0skht0rj5nv53jzc5srk0ng80000gn/T/serverless-rps.Lw1V9oQ8

Testing create game...
Player1 ([email protected]) created game 5bccdbbb-b5a9-4244-9d6b-7f24d424e9ed

Testing join game...
Player2 ([email protected]) joined game 5bccdbbb-b5a9-4244-9d6b-7f24d424e9ed

Testing make move...
Player1 ([email protected]) played: paper
Player2 ([email protected]) played: scissors
The winner is: [email protected]

Testing get game...
The game 5bccdbbb-b5a9-4244-9d6b-7f24d424e9ed looks fine

Testing get games (with state=ended)...
Warning: status code was 504 (Gateway Timeout), retrying...
There are 6 games in state ended

Tests are done

The first API Gateway calls on cold instances often result in a timeout. As you can see above, the test script handles retries in those cases (max 5).

The created game is not cleaned up, so you might want to:

  • set --player1 and/or --player2 to something you recognize
  • manually delete the games created by your tests from the DynamoDB console

Where to go from here

If you want to code some more, here are some ideas.

Error Handling

The current code template didn't include any error handling. Improve the code and map errors to HTTP status code in the API Gateway.

Implement highscore

Implement a highscore feature for the rps game. Checkout the 'dynamo-process-stream' blueprint in AWS Lmabda. It's an DynamoDB trigger that logs updates to a table. Implement a lambda function that gets the updates, and when receiving an update for a game that's ended, update a highscore collection, i.e, "player, wins, losts, ties".

Upload player image

Upload player image to s3, write a lambda function that listens to s3:ObjectCreated:* events, scale the upload image and displays it in the highscore list. See AWS Lambda Walkthrough 1: Process S3 Events (Java)

Use API Gateway as Service Proxy to DynamoDB

Instead of using AWS Lambda, try to use API Gateway as service proxy to DynamoDB. Check Making HTTP Requests to DynamoDB and Walkthrough: API Gateway and an AWS Service Proxy

Slap a web app frontend onto it

Implement a web app frontend. This requires CORS support

serverless-rps-lab's People

Contributors

erikogenvik avatar kobmic avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.