diff --git a/pom.xml b/pom.xml index 115aefc32..7f1ff2208 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,11 @@ 3.3.0 1.7.21 1.1.7 + 1.1.0 + 1.11.289 + 1.0.0 + 2.0.1 + 2.8.5 abstract-factory @@ -154,6 +159,7 @@ eip-aggregator retry trampoline + serverless diff --git a/serverless/README.md b/serverless/README.md new file mode 100644 index 000000000..b0a29fe66 --- /dev/null +++ b/serverless/README.md @@ -0,0 +1,199 @@ +--- +layout: pattern +title: serverless +folder: serverless +permalink: /patterns/serverless/ +categories: Architectural +tags: + - Java + - Difficulty-Intermediate +--- + +## Serverless + +Serverless eliminates the need to plan for infrastructure and let's you focus on your +application. + +Following are optimization katas you should be aware of while building a serverless +applications + +* The Lean function + * Concise logic - Use functions to transform, not transport (utilize some of the + integration available from the provider to transport), and make sure you read only + what you need + * Efficient/single purpose code - avoid conditional/routing logic and break down + into individual functions, avoid "fat"/monolithic functions and control the + dependencies in the function deployment package to reduce the load time for your + function + * ephemeral environment - Utilize container start for expensive initializations +* Eventful Invocations + * Succinct payloads - Scrutinize the event as much as possible, and watch for + payload constraints (async - 128K) + * resilient routing - Understand retry policies and leverage dead letter queues + (SQS or SNS for replays) and remember retries count as invocations + * concurrent execution - lambda thinks of it's scale in terms of concurrency and + its not request based/duration based. Lambda will spin up the number of instances + based on the request. +* Coordinated calls + * Decoupled via APIs - best practice to setup your application is to have API's as + contracts that ensures separation of concerns + * scale-matched downstream - make sure when Lambda is calling downstream + components, you are matching scale configuration to it (by specifying max + concurrency based on downstream services) + * secured - Always ask a question, do I need a VPC? +* Serviceful operations + * Automated - use automated tools to manage and maintain the stack + * monitored applications - use monitoring services to get holistic view of your + serverless applications + +## Intent + +Whether to reduce your infrastructure costs, shrink the time you spend on ops tasks, +simplify your deployment processes, reach infinite scalability, serverless cuts time +to market in half. + +## Explanation + +Serverless computing is a cloud computing execution model in which the cloud provider +dynamically manages the allocation of machine resources. Pricing is based on the +actual amount of resources consumed by an application, rather than on pre-purchased +units of capacity. + +## Serverless framework + +[Serverless](https://serverless.com/) is a toolkit for deploying and operating serverless architectures. + +## (Function as a Service or "FaaS") + +The term ‘Serverless’ is confusing since with such applications there are both server +hardware and server processes running somewhere, but the difference to normal +approaches is that the organization building and supporting a ‘Serverless’ application + is not looking after the hardware or the processes - they are outsourcing this to a vendor. + +Some of the Serverless Cloud Providers are + +![https://serverless.com/framework/docs/providers/aws/](./etc/aws-black.png "aws") +![https://serverless.com/framework/docs/providers/azure/](./etc/azure-black.png "azure") +![https://serverless.com/framework/docs/providers/openwhisk/](./etc/openwhisk-black.png "openwhisk") +![https://serverless.com/framework/docs/providers/google/](./etc/gcf-black.png "google") +![https://serverless.com/framework/docs/providers/kubeless/](./etc/kubeless-logos-black.png "kubeless") +![https://serverless.com/framework/docs/providers/spotinst/](./etc/spotinst-logos-black-small.png "spotinst") +![https://serverless.com/framework/docs/providers/webtasks/](./etc/webtask-small-grayscale.png "webtask") +... + +Anything that triggers an Lambda Function to execute is regarded by the Framework as +an Event. Most of the Serverless Cloud Providers support following Events +- Http +- PubSub Events +- scheduled + +AWS supports processing event generated from AWS Services (S3/Cloudwatch/etc) and +using aws as a compute engine is our first choice. + +## (Backend as a Service or "BaaS") +This example creates a backend for ‘persons’ collection which uses DynamoDB NoSQL +database service also provided by Amazon. + +## AWS lambda function implementation + +[AWS Lambda SDK](https://aws.amazon.com/sdk-for-java/) provides pre-defined interface +`com.amazonaws.services.lambda.runtime +.RequestHandler` to implement our lambda function. + +```java +public class LambdaInfoApiHandler implements RequestHandler, ApiGatewayResponse> { + + private static final Logger LOG = Logger.getLogger(LambdaInfoApiHandler.class); + private static final Integer SUCCESS_STATUS_CODE = 200; + + + @Override + public ApiGatewayResponse handleRequest(Map input, Context context) { + + } +} +``` +handleRequest method is where the function code is implemented. Context provides +useful information about Lambda execution environment. AWS Lambda function needs a +deployment package. This package is either a .zip or .jar file that contains all the +dependencies of the function. + +`serverless.yml` contains configuration to manage deployments for your functions. + +## Run example in local + +# Pre-requisites +* Node.js v6.5.0 or later. +* Serverless CLI v1.9.0 or later. You can run npm install -g serverless to install it. +* An AWS account. If you don't already have one, you can sign up for a free trial that includes 1 million free Lambda requests per month. +* [Set-up](https://serverless.com/framework/docs/providers/aws/guide/credentials/) your Provider Credentials. Watch the video on setting up credentials + +# build and deploy + +* `cd serverless` +* `mvn clean package` +* `serverless deploy --stage=dev --verbose` + +Based on the configuration in `serverless.yml` serverless framework creates following +resources +* CloudFormation stack for S3 (ServerlessDeploymentBucket) +* IAM Role (IamRoleLambdaExecution) +* CloudWatch (log groups) +* API Gateway (ApiGatewayRestApi) +* Lambda function +* DynamoDB collection + +The command will print out Stack Outputs which looks something like this + +```yaml +endpoints: + GET - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/info + POST - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/api/person + GET - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/api/person/{id} + +``` + +```yaml +CurrentTimeLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:xxxxxxxxxxx:function:lambda-info-http-endpoint-dev-currentTime:4 +ServiceEndpoint: https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev +ServerlessDeploymentBucketName: lambda-info-http-endpoin-serverlessdeploymentbuck-2u8uz2i7cap2 +``` +access the endpoint to invoke the function. + +Use the following cURL commands to test the endpoints + +```cURL +curl -X GET \ + https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/info \ + -H 'cache-control: no-cache' +``` + +```cURL +curl -X POST \ + https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/api/person \ + -H 'cache-control: no-cache' \ + -H 'content-type: application/json' \ + -d '{ + "firstName": "Thor", + "lastName": "Odinson", + "address": { + "addressLineOne": "1 Odin ln", + "addressLineTwo": "100", + "city": "Asgard", + "state": "country of the Gods", + "zipCode": "00001" + } +}' +``` + +```cURL +curl -X GET \ + https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/api/person/{id} \ + -H 'cache-control: no-cache' +``` + +## Credits + +* [serverless docs](https://serverless.com/framework/docs/) +* [Serverless Architectures](https://martinfowler.com/articles/serverless.html) +* [Serverless Black Belt](https://youtu.be/oQFORsso2go) \ No newline at end of file diff --git a/serverless/etc/aws-black.png b/serverless/etc/aws-black.png new file mode 100644 index 000000000..7b5ba3178 Binary files /dev/null and b/serverless/etc/aws-black.png differ diff --git a/serverless/etc/azure-black.png b/serverless/etc/azure-black.png new file mode 100644 index 000000000..95ceb7e82 Binary files /dev/null and b/serverless/etc/azure-black.png differ diff --git a/serverless/etc/gcf-black.png b/serverless/etc/gcf-black.png new file mode 100644 index 000000000..b4e22982c Binary files /dev/null and b/serverless/etc/gcf-black.png differ diff --git a/serverless/etc/kubeless-logos-black.png b/serverless/etc/kubeless-logos-black.png new file mode 100644 index 000000000..dd6523c51 Binary files /dev/null and b/serverless/etc/kubeless-logos-black.png differ diff --git a/serverless/etc/openwhisk-black.png b/serverless/etc/openwhisk-black.png new file mode 100644 index 000000000..e70139088 Binary files /dev/null and b/serverless/etc/openwhisk-black.png differ diff --git a/serverless/etc/spotinst-logos-black-small.png b/serverless/etc/spotinst-logos-black-small.png new file mode 100644 index 000000000..b748f80c7 Binary files /dev/null and b/serverless/etc/spotinst-logos-black-small.png differ diff --git a/serverless/etc/webtask-small-grayscale.png b/serverless/etc/webtask-small-grayscale.png new file mode 100644 index 000000000..8e2180935 Binary files /dev/null and b/serverless/etc/webtask-small-grayscale.png differ diff --git a/serverless/pom.xml b/serverless/pom.xml new file mode 100644 index 000000000..cfaf6f23c --- /dev/null +++ b/serverless/pom.xml @@ -0,0 +1,133 @@ + + + + 4.0.0 + serverless + + com.iluwatar + java-design-patterns + 1.20.0-SNAPSHOT + + + + + com.amazonaws + aws-lambda-java-core + ${aws-lambda-core.version} + + + com.amazonaws + aws-java-sdk-dynamodb + ${aws-java-sdk-dynamodb.version} + + + com.amazonaws + aws-java-sdk-s3 + + + com.amazonaws + aws-java-sdk-kms + + + + + com.amazonaws + aws-lambda-java-events + ${aws-lambda-java-events.version} + + + com.amazonaws + aws-lambda-java-log4j + ${aws-lambda-log4j.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + junit + junit + test + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + ${project.artifactId} + + + + + + + + \ No newline at end of file diff --git a/serverless/serverless.yml b/serverless/serverless.yml new file mode 100644 index 000000000..c463395df --- /dev/null +++ b/serverless/serverless.yml @@ -0,0 +1,102 @@ +# +# The MIT License +# Copyright (c) 2014 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +service: serverless-services + +frameworkVersion: ">=1.2.0 <2.0.0" + +provider: + name: aws + runtime: java8 + usagePlan: + quota: + limit: 500 + offset: 2 + period: MONTH + throttle: + burstLimit: 20 + rateLimit: 10 + +package: + artifact: target/serverless.jar + +functions: + lambdaInfoApi: + handler: com.iluwatar.serverless.faas.api.LambdaInfoApiHandler + events: + - http: + path: info + method: get + + savePerson: + handler: com.iluwatar.serverless.baas.api.SavePersonApiHandler + events: + - http: + path: api/person + method: post + cors: true + integration: lambda-proxy + + getPerson: + handler: com.iluwatar.serverless.baas.api.FindPersonApiHandler + events: + - http: + path: api/person/{id} + method: get + cors: true + integration: lambda-proxy + +resources: + Resources: + DynamoDbTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: persons + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 + DynamoDBIamPolicy: + Type: AWS::IAM::Policy + DependsOn: DynamoDbTable + Properties: + PolicyName: lambda-dynamodb + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:UpdateItem + - dynamodb:DeleteItem + - dynamodb:Query + - dynamodb:Scan + Resource: arn:aws:dynamodb:*:*:table/persons + Roles: + - Ref: IamRoleLambdaExecution \ No newline at end of file diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java b/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java new file mode 100644 index 000000000..ac27a9a4a --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java @@ -0,0 +1,101 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.baas.api; + +import com.amazonaws.regions.Regions; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * abstract dynamodb handler + * @param - serializable collection + */ +public abstract class AbstractDynamoDbHandler { + private DynamoDBMapper dynamoDbMapper; + + private ObjectMapper objectMapper; + + public AbstractDynamoDbHandler() { + this.initAmazonDynamoDb(); + this.objectMapper = new ObjectMapper(); + } + + private void initAmazonDynamoDb() { + AmazonDynamoDB amazonDynamoDb = AmazonDynamoDBClientBuilder + .standard() + .withRegion(Regions.US_EAST_1) + .build(); + + this.dynamoDbMapper = new DynamoDBMapper(amazonDynamoDb); + } + + protected DynamoDBMapper getDynamoDbMapper() { + return this.dynamoDbMapper; + } + + protected ObjectMapper getObjectMapper() { + return objectMapper; + } + + public void setDynamoDbMapper(DynamoDBMapper dynamoDbMapper) { + this.dynamoDbMapper = dynamoDbMapper; + } + + protected Map headers() { + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + + return headers; + } + + /** + * API Gateway response + * + * @param statusCode - status code + * @param body - Object body + * @return - api gateway proxy response + */ + protected APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent(Integer statusCode, T body) { + APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent = + new APIGatewayProxyResponseEvent().withHeaders(headers()); + try { + apiGatewayProxyResponseEvent + .withStatusCode(statusCode) + .withBody(getObjectMapper() + .writeValueAsString(body)); + + } catch (JsonProcessingException jsonProcessingException) { + throw new RuntimeException(jsonProcessingException); + } + + return apiGatewayProxyResponseEvent; + } +} diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/api/FindPersonApiHandler.java b/serverless/src/main/java/com/iluwatar/serverless/baas/api/FindPersonApiHandler.java new file mode 100644 index 000000000..2eb1613f8 --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/api/FindPersonApiHandler.java @@ -0,0 +1,51 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.baas.api; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.iluwatar.serverless.baas.model.Person; +import org.apache.log4j.Logger; + +/** + * find person from persons collection + * Created by dheeraj.mummar on 3/5/18. + */ +public class FindPersonApiHandler extends AbstractDynamoDbHandler + implements RequestHandler { + + private static final Logger LOG = Logger.getLogger(FindPersonApiHandler.class); + private static final Integer SUCCESS_STATUS_CODE = 200; + + @Override + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent, + Context context) { + LOG.info(apiGatewayProxyRequestEvent.getPathParameters()); + Person person = this.getDynamoDbMapper().load(Person.class, apiGatewayProxyRequestEvent + .getPathParameters().get("id")); + + return apiGatewayProxyResponseEvent(SUCCESS_STATUS_CODE, person); + } +} diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/api/SavePersonApiHandler.java b/serverless/src/main/java/com/iluwatar/serverless/baas/api/SavePersonApiHandler.java new file mode 100644 index 000000000..f00d28b96 --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/api/SavePersonApiHandler.java @@ -0,0 +1,61 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.baas.api; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.iluwatar.serverless.baas.model.Person; +import org.apache.log4j.Logger; + +import java.io.IOException; + +/** + * save person into persons collection + * Created by dheeraj.mummar on 3/4/18. + */ +public class SavePersonApiHandler extends AbstractDynamoDbHandler + implements RequestHandler { + + private static final Logger LOG = Logger.getLogger(SavePersonApiHandler.class); + private static final Integer CREATED_STATUS_CODE = 201; + private static final Integer BAD_REQUEST_STATUS_CODE = 400; + + @Override + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent + apiGatewayProxyRequestEvent, Context context) { + APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent; + Person person; + try { + person = getObjectMapper().readValue(apiGatewayProxyRequestEvent.getBody(), Person.class); + getDynamoDbMapper().save(person); + apiGatewayProxyResponseEvent = apiGatewayProxyResponseEvent(CREATED_STATUS_CODE, person); + } catch (IOException ioException) { + LOG.error("unable to parse body", ioException); + apiGatewayProxyResponseEvent = apiGatewayProxyResponseEvent(BAD_REQUEST_STATUS_CODE, null); + } + + return apiGatewayProxyResponseEvent; + } +} diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/model/Address.java b/serverless/src/main/java/com/iluwatar/serverless/baas/model/Address.java new file mode 100644 index 000000000..7331cea2b --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/model/Address.java @@ -0,0 +1,141 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.baas.model; + +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBDocument; + +import java.io.Serializable; + +/** + * Address class + * Created by dheeraj.mummarareddy on 3/4/18. + */ +@DynamoDBDocument +public class Address implements Serializable { + + private static final long serialVersionUID = 6760844284799736970L; + + private String addressLineOne; + private String addressLineTwo; + private String city; + private String state; + private String zipCode; + + @DynamoDBAttribute(attributeName = "addressLineOne") + public String getAddressLineOne() { + return addressLineOne; + } + + public void setAddressLineOne(String addressLineOne) { + this.addressLineOne = addressLineOne; + } + + @DynamoDBAttribute(attributeName = "addressLineTwo") + public String getAddressLineTwo() { + return addressLineTwo; + } + + public void setAddressLineTwo(String addressLineTwo) { + this.addressLineTwo = addressLineTwo; + } + + @DynamoDBAttribute(attributeName = "city") + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + @DynamoDBAttribute(attributeName = "state") + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + @DynamoDBAttribute(attributeName = "zipCode") + public String getZipCode() { + return zipCode; + } + + public void setZipCode(String zipCode) { + this.zipCode = zipCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + Address address = (Address) o; + + if (addressLineOne != null ? !addressLineOne.equals(address.addressLineOne) : + address.addressLineOne != null) { + return false; + } + + if (addressLineTwo != null ? !addressLineTwo.equals(address.addressLineTwo) : + address.addressLineTwo != null) { + return false; + } + + if (city != null ? !city.equals(address.city) : address.city != null) { + return false; + } + if (state != null ? !state.equals(address.state) : address.state != null) { + return false; + } + return zipCode != null ? zipCode.equals(address.zipCode) : address.zipCode == null; + } + + @Override + public int hashCode() { + int result = addressLineOne != null ? addressLineOne.hashCode() : 0; + result = 31 * result + (addressLineTwo != null ? addressLineTwo.hashCode() : 0); + result = 31 * result + (city != null ? city.hashCode() : 0); + result = 31 * result + (state != null ? state.hashCode() : 0); + result = 31 * result + (zipCode != null ? zipCode.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Address{" + + "addressLineOne='" + addressLineOne + '\'' + + ", addressLineTwo='" + addressLineTwo + '\'' + + ", city='" + city + '\'' + + ", state='" + state + '\'' + + ", zipCode='" + zipCode + '\'' + + '}'; + } +} diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/model/Person.java b/serverless/src/main/java/com/iluwatar/serverless/baas/model/Person.java new file mode 100644 index 000000000..75604999e --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/model/Person.java @@ -0,0 +1,124 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.baas.model; + +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; + +/** + * Person class + * Created by dheeraj.mummarareddy on 3/4/18. + */ +@DynamoDBTable(tableName = "persons") +public class Person implements Serializable { + + private static final long serialVersionUID = -3413087924608627075L; + + private String id; + private String firstName; + private String lastName; + private Address address; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @DynamoDBHashKey(attributeName = "id") + @DynamoDBAutoGeneratedKey + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @DynamoDBAttribute(attributeName = "firstName") + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + @DynamoDBAttribute(attributeName = "lastName") + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @DynamoDBAttribute(attributeName = "address") + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Person person = (Person) o; + + if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) { + return false; + } + + if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) { + return false; + } + + return address != null ? address.equals(person.address) : person.address == null; + } + + @Override + public int hashCode() { + int result = firstName != null ? firstName.hashCode() : 0; + result = 31 * result + (lastName != null ? lastName.hashCode() : 0); + result = 31 * result + (address != null ? address.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Person{" + + "id='" + id + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", address=" + address + + '}'; + } +} diff --git a/serverless/src/main/java/com/iluwatar/serverless/faas/ApiGatewayResponse.java b/serverless/src/main/java/com/iluwatar/serverless/faas/ApiGatewayResponse.java new file mode 100644 index 000000000..98e2de64f --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/faas/ApiGatewayResponse.java @@ -0,0 +1,166 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.faas; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.Serializable; +import java.util.Map; + +/** + * Api gateway response + * + * @param serializable object + */ +public class ApiGatewayResponse implements Serializable { + + private static final long serialVersionUID = 1181159426782844892L; + + private final Integer statusCode; + private final String body; + private final Map headers; + private final Boolean isBase64Encoded; + + /** + * api gateway response + * + * @param statusCode - http status code + * @param body - response body + * @param headers - response headers + * @param isBase64Encoded - base64Encoded flag + */ + public ApiGatewayResponse(Integer statusCode, String body, Map headers, + Boolean isBase64Encoded) { + this.statusCode = statusCode; + this.body = body; + this.headers = headers; + this.isBase64Encoded = isBase64Encoded; + } + + /** + * http status code + * + * @return statusCode - http status code + */ + public Integer getStatusCode() { + return statusCode; + } + + /** + * response body + * + * @return string body + */ + public String getBody() { + return body; + } + + /** + * response headers + * + * @return response headers + */ + public Map getHeaders() { + return headers; + } + + /** + * base64Encoded flag, API Gateway expects the property to be called "isBase64Encoded" + * + * @return base64Encoded flag + */ + public Boolean isBase64Encoded() { + return isBase64Encoded; + } + + /** + * ApiGatewayResponse Builder class + * @param Serializable object + */ + public static class ApiGatewayResponseBuilder { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private Integer statusCode; + private T body; + private Map headers; + private Boolean isBase64Encoded; + + /** + * http status code + * @param statusCode - http status code + * @return ApiGatewayResponseBuilder + */ + public ApiGatewayResponseBuilder statusCode(Integer statusCode) { + this.statusCode = statusCode; + return this; + } + + /** + * Serializable body + * @param body - Serializable object + * @return ApiGatewayResponseBuilder + */ + public ApiGatewayResponseBuilder body(T body) { + this.body = body; + return this; + } + + /** + * response headers + * @param headers - response headers + * @return ApiGatewayResponseBuilder + */ + public ApiGatewayResponseBuilder headers(Map headers) { + this.headers = headers; + return this; + } + + /** + * base64Encoded glag + * @param isBase64Encoded - base64Encoded flag + * @return ApiGatewayResponseBuilder + */ + public ApiGatewayResponseBuilder base64Encoded(Boolean isBase64Encoded) { + this.isBase64Encoded = isBase64Encoded; + return this; + } + + /** + * build ApiGatewayResponse + * + * @return ApiGatewayResponse + */ + public ApiGatewayResponse build() { + String strBody = null; + if (this.body != null) { + try { + strBody = OBJECT_MAPPER.writeValueAsString(body); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + return new ApiGatewayResponse(this.statusCode, strBody, this.headers, this.isBase64Encoded); + } + } +} diff --git a/serverless/src/main/java/com/iluwatar/serverless/faas/LambdaInfo.java b/serverless/src/main/java/com/iluwatar/serverless/faas/LambdaInfo.java new file mode 100644 index 000000000..03932a9f0 --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/faas/LambdaInfo.java @@ -0,0 +1,140 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.faas; + +import java.io.Serializable; + +/** + * Lambda context information + */ +public class LambdaInfo implements Serializable { + + private static final long serialVersionUID = 3936130599040848923L; + + private String awsRequestId; + private String logGroupName; + private String logStreamName; + private String functionName; + private String functionVersion; + private Integer memoryLimitInMb; + + public String getAwsRequestId() { + return awsRequestId; + } + + public void setAwsRequestId(String awsRequestId) { + this.awsRequestId = awsRequestId; + } + + public String getLogGroupName() { + return logGroupName; + } + + public void setLogGroupName(String logGroupName) { + this.logGroupName = logGroupName; + } + + public String getLogStreamName() { + return logStreamName; + } + + public void setLogStreamName(String logStreamName) { + this.logStreamName = logStreamName; + } + + public String getFunctionName() { + return functionName; + } + + public void setFunctionName(String functionName) { + this.functionName = functionName; + } + + public String getFunctionVersion() { + return functionVersion; + } + + public void setFunctionVersion(String functionVersion) { + this.functionVersion = functionVersion; + } + + public Integer getMemoryLimitInMb() { + return memoryLimitInMb; + } + + public void setMemoryLimitInMb(Integer memoryLimitInMb) { + this.memoryLimitInMb = memoryLimitInMb; + } + + @Override + public String toString() { + return "LambdaInfo{" + + "awsRequestId='" + awsRequestId + '\'' + + ", logGroupName='" + logGroupName + '\'' + + ", logStreamName='" + logStreamName + '\'' + + ", functionName='" + functionName + '\'' + + ", functionVersion='" + functionVersion + '\'' + + ", memoryLimitInMb=" + memoryLimitInMb + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LambdaInfo that = (LambdaInfo) o; + + if (awsRequestId != null ? !awsRequestId.equals(that.awsRequestId) : that.awsRequestId != null) { + return false; + } + if (logGroupName != null ? !logGroupName.equals(that.logGroupName) : that.logGroupName != null) { + return false; + } + if (logStreamName != null ? !logStreamName.equals(that.logStreamName) : that.logStreamName != null) { + return false; + } + if (functionName != null ? !functionName.equals(that.functionName) : that.functionName != null) { + return false; + } + if (functionVersion != null ? !functionVersion.equals(that.functionVersion) : that.functionVersion != null) { + return false; + } + return memoryLimitInMb != null ? memoryLimitInMb.equals(that.memoryLimitInMb) : that.memoryLimitInMb == null; + } + + @Override + public int hashCode() { + int result = awsRequestId != null ? awsRequestId.hashCode() : 0; + result = 31 * result + (logGroupName != null ? logGroupName.hashCode() : 0); + result = 31 * result + (logStreamName != null ? logStreamName.hashCode() : 0); + result = 31 * result + (functionName != null ? functionName.hashCode() : 0); + result = 31 * result + (functionVersion != null ? functionVersion.hashCode() : 0); + result = 31 * result + (memoryLimitInMb != null ? memoryLimitInMb.hashCode() : 0); + return result; + } +} diff --git a/serverless/src/main/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandler.java b/serverless/src/main/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandler.java new file mode 100644 index 000000000..67e1835f9 --- /dev/null +++ b/serverless/src/main/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandler.java @@ -0,0 +1,83 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.faas.api; + +import com.iluwatar.serverless.faas.ApiGatewayResponse; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.iluwatar.serverless.faas.LambdaInfo; +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; + +/** + * LambdaInfoApiHandler - simple api to get lambda context + * Created by dheeraj.mummar on 2/5/18. + */ +public class LambdaInfoApiHandler implements RequestHandler, ApiGatewayResponse> { + + private static final Logger LOG = Logger.getLogger(LambdaInfoApiHandler.class); + private static final Integer SUCCESS_STATUS_CODE = 200; + + + @Override + public ApiGatewayResponse handleRequest(Map input, Context context) { + BasicConfigurator.configure(); + LOG.info("received: " + input); + + return new ApiGatewayResponse + .ApiGatewayResponseBuilder() + .headers(headers()) + .statusCode(SUCCESS_STATUS_CODE) + .body(lambdaInfo(context)) + .build(); + + } + + /** + * lambdaInfo + * @param context - Lambda context + * @return LambdaInfo + */ + private LambdaInfo lambdaInfo(Context context) { + LambdaInfo lambdaInfo = new LambdaInfo(); + lambdaInfo.setAwsRequestId(context.getAwsRequestId()); + lambdaInfo.setFunctionName(context.getFunctionName()); + lambdaInfo.setFunctionVersion(context.getFunctionVersion()); + lambdaInfo.setLogGroupName(context.getLogGroupName()); + lambdaInfo.setLogStreamName(context.getLogStreamName()); + lambdaInfo.setMemoryLimitInMb(context.getMemoryLimitInMB()); + + return lambdaInfo; + } + + private Map headers() { + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + + return headers; + } +} diff --git a/serverless/src/main/resources/log4j.properties b/serverless/src/main/resources/log4j.properties new file mode 100644 index 000000000..15a25e61b --- /dev/null +++ b/serverless/src/main/resources/log4j.properties @@ -0,0 +1,29 @@ +# +# The MIT License +# Copyright (c) 2014 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +log = . +log4j.rootLogger = DEBUG, LAMBDA + +log4j.appender.LAMBDA=com.amazonaws.services.lambda.runtime.log4j.LambdaAppender +log4j.appender.LAMBDA.layout=org.apache.log4j.PatternLayout +log4j.appender.LAMBDA.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} <%X{AWSRequestId}> %-5p %c:%L - %m%n diff --git a/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java new file mode 100644 index 000000000..561281ab3 --- /dev/null +++ b/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java @@ -0,0 +1,72 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.baas.api; + +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.iluwatar.serverless.baas.api.FindPersonApiHandler; +import com.iluwatar.serverless.baas.api.SavePersonApiHandler; +import com.iluwatar.serverless.baas.model.Person; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Collections; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Unit tests for FindPersonApiHandler + * Created by dheeraj.mummar on 3/5/18. + */ +@RunWith(MockitoJUnitRunner.class) +public class FindPersonApiHandlerTest { + + private FindPersonApiHandler findPersonApiHandler; + + @Mock + private DynamoDBMapper dynamoDbMapper; + + @Before + public void setUp() { + this.findPersonApiHandler = new FindPersonApiHandler(); + this.findPersonApiHandler.setDynamoDbMapper(dynamoDbMapper); + } + + @Test + public void handleRequest() { + findPersonApiHandler.handleRequest(apiGatewayProxyRequestEvent(), mock(Context.class)); + verify(dynamoDbMapper, times(1)).load(Person.class, "37e7a1fe-3544-473d-b764-18128f02d72d"); + } + + private APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent() { + return new APIGatewayProxyRequestEvent() + .withPathParamters(Collections + .singletonMap("id", "37e7a1fe-3544-473d-b764-18128f02d72d")); + } +} diff --git a/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java new file mode 100644 index 000000000..83008bdff --- /dev/null +++ b/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java @@ -0,0 +1,101 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.baas.api; + +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.iluwatar.serverless.baas.api.SavePersonApiHandler; +import com.iluwatar.serverless.baas.model.Address; +import com.iluwatar.serverless.baas.model.Person; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.mockito.Mockito.*; + +/** + * Unit tests for SavePersonApiHandler + * Created by dheeraj.mummar on 3/4/18. + */ +@RunWith(MockitoJUnitRunner.class) +public class SavePersonApiHandlerTest { + + private SavePersonApiHandler savePersonApiHandler; + + @Mock + private DynamoDBMapper dynamoDbMapper; + + private ObjectMapper objectMapper = new ObjectMapper(); + + @Before + public void setUp() { + this.savePersonApiHandler = new SavePersonApiHandler(); + this.savePersonApiHandler.setDynamoDbMapper(dynamoDbMapper); + } + + @Test + public void handleRequestSavePersonSuccessful() throws JsonProcessingException { + Person person = newPerson(); + APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent = + this.savePersonApiHandler + .handleRequest(apiGatewayProxyRequestEvent(objectMapper.writeValueAsString(person)), mock(Context.class)); + verify(dynamoDbMapper, times(1)).save(person); + Assert.assertNotNull(apiGatewayProxyResponseEvent); + Assert.assertEquals(new Integer(201), apiGatewayProxyResponseEvent.getStatusCode()); + } + + @Test + public void handleRequestSavePersonException() { + APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent = + this.savePersonApiHandler + .handleRequest(apiGatewayProxyRequestEvent("invalid sample request"), mock(Context.class)); + Assert.assertNotNull(apiGatewayProxyResponseEvent); + Assert.assertEquals(new Integer(400), apiGatewayProxyResponseEvent.getStatusCode()); + } + + private APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent(String body) { + APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent = new APIGatewayProxyRequestEvent(); + return apiGatewayProxyRequestEvent.withBody(body); + } + + private Person newPerson() { + Person person = new Person(); + person.setFirstName("Thor"); + person.setLastName("Odinson"); + Address address = new Address(); + address.setAddressLineOne("1 Odin ln"); + address.setCity("Asgard"); + address.setState("country of the Gods"); + address.setZipCode("00001"); + person.setAddress(address); + + return person; + } +} diff --git a/serverless/src/test/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandlerTest.java new file mode 100644 index 000000000..876489ed2 --- /dev/null +++ b/serverless/src/test/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandlerTest.java @@ -0,0 +1,49 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.serverless.faas.api; + +import com.amazonaws.services.lambda.runtime.Context; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for LambdaInfoApiHandler + */ +@RunWith(MockitoJUnitRunner.class) +public class LambdaInfoApiHandlerTest { + + @Test + public void handleRequestWithMockContext() { + LambdaInfoApiHandler lambdaInfoApiHandler = new LambdaInfoApiHandler(); + Context context = mock(Context.class); + when(context.getAwsRequestId()).thenReturn("mock aws request id"); + + assertThat(lambdaInfoApiHandler.handleRequest(null, context), notNullValue()); + } +} \ No newline at end of file