├── .github
├── actions
│ └── sam-build-and-deploy
│ │ └── action.yml
└── workflows
│ └── java-deploy-pipeline.yaml
├── .gitignore
├── LICENSE
├── README.md
├── infrastructure
├── cdk
│ ├── .gitignore
│ ├── README.md
│ ├── cdk.json
│ ├── pom.xml
│ └── src
│ │ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── product
│ │ │ └── infrastructure
│ │ │ ├── DatabaseStack.java
│ │ │ ├── DatabaseStackProps.java
│ │ │ ├── InfrastructureApp.java
│ │ │ ├── InfrastructureStack.java
│ │ │ └── NetworkingStack.java
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── product
│ │ └── infrastructure
│ │ └── InfrastructureTest.java
└── db-setup
│ ├── dependency-reduced-pom.xml
│ ├── pom.xml
│ └── src
│ └── main
│ ├── java
│ └── com
│ │ └── amazon
│ │ └── aws
│ │ ├── AwsSecret.java
│ │ └── DBSetupHandler.java
│ └── resources
│ └── setup.sql
├── pom.xml
├── src
├── main
│ ├── java
│ │ └── com
│ │ │ └── product
│ │ │ └── api
│ │ │ ├── ApplicationConfiguration.java
│ │ │ ├── ApplicationProperties.java
│ │ │ ├── AwsSecret.java
│ │ │ ├── JpaConfiguration.java
│ │ │ ├── ProductApiApplication.java
│ │ │ ├── ProductController.java
│ │ │ ├── SnapshotConfiguration.java
│ │ │ ├── StreamLambdaHandler.java
│ │ │ └── core
│ │ │ ├── IProductRepository.java
│ │ │ ├── Product.java
│ │ │ ├── ProductDTO.java
│ │ │ └── ProductService.java
│ └── resources
│ │ ├── application.properties
│ │ ├── log4j2.xml
│ │ └── schema-postgres.sql
└── test
│ └── java
│ └── ProductApiIT.java
└── template.yaml
/.github/actions/sam-build-and-deploy/action.yml:
--------------------------------------------------------------------------------
1 | name: "SAM Build & Deploy"
2 | description: "Build & Deploy SAM application"
3 | inputs:
4 | aws-access-key:
5 | required: true
6 | description: "AWS Access Key"
7 | aws-secret-key:
8 | required: true
9 | description: "AWS Secret Key"
10 | aws-region:
11 | required: true
12 | description: "AWS Region"
13 | template-file-path:
14 | required: true
15 | description: "Path to the SAM template file"
16 | stack-name:
17 | required: true
18 | description: "The name of the CloudFormation stack to deploy"
19 | s3-bucket-name:
20 | required: true
21 | description: "The name of the S3 bucket to store SAM artefacts"
22 | security-group-id:
23 | required: true
24 | description: "The ID of the security group to use"
25 | subnet-1-id:
26 | required: true
27 | description: "The ID of the first subnet to use"
28 | subnet-2-id:
29 | required: true
30 | description: "The ID of the second subnet to use"
31 | secret-name:
32 | required: true
33 | description: "The name of the secret holding the database credentials"
34 |
35 | runs:
36 | using: "composite"
37 | steps:
38 | - uses: actions/setup-python@v2
39 | - uses: actions/setup-java@v2
40 | with:
41 | java-version: 11
42 | - uses: aws-actions/setup-sam@v1
43 | - uses: aws-actions/configure-aws-credentials@v1
44 | with:
45 | aws-access-key-id: ${{ inputs.aws-access-key }}
46 | aws-secret-access-key: ${{ inputs.aws-secret-key }}
47 | aws-region: ${{ inputs.aws-region }}
48 |
49 | # Build and deploy ARM
50 | - run: sam build -t ${{ inputs.template-file-path }}
51 | shell: bash
52 | - run: sam deploy --no-confirm-changeset --no-fail-on-empty-changeset --stack-name ${{inputs.stack-name}} --s3-prefix ${{inputs.stack-name}} --s3-bucket ${{ inputs.s3-bucket-name }} --capabilities CAPABILITY_IAM --parameter-overrides ParameterKey=SecurityGroupId,ParameterValue=${{ inputs.security-group-id }} ParameterKey=Subnet1Id,ParameterValue=${{ inputs.subnet-1-id }} ParameterKey=Subnet2Id,ParameterValue=${{ inputs.subnet-2-id }} ParameterKey=DatabaseSecretName,ParameterValue=${{ inputs.secret-name }}
53 | shell: bash
--------------------------------------------------------------------------------
/.github/workflows/java-deploy-pipeline.yaml:
--------------------------------------------------------------------------------
1 | name: JavaSamDeploy
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 | paths:
9 | - src
10 | jobs:
11 |
12 | build-deploy:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Build and Deploy
17 | uses: ./.github/actions/sam-build-and-deploy
18 | with:
19 | aws-access-key: ${{ secrets.AWS_ACCESS_KEY_ID }}
20 | aws-secret-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
21 | aws-region: ${{ secrets.AWS_REGION }}
22 | template-file-path: ./template.yaml
23 | stack-name: java-spring
24 | s3-bucket-name: aws-sam-cli-managed-default-samclisourcebucket-1kgurv0ac87fw
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
25 | samconfig.toml
26 | **/.aws-sam/*
27 | **/.idea/*
28 |
29 | .classpath
30 | .project
31 | .settings/
32 | target/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 James Eastham
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpringBoot on Lambda
2 |
3 | Example repository for running a SpringBoot API on AWS Lambda. This GitHub repo coincides with a [series on Youtube](https://www.youtube.com/watch?v=A1rYiHTy9Lg&list=PLCOG9xkUD90IDm9tcY-5nMK6X6g8SD-Sz) walking through how to build and deploy SpringBoot API's on Lambda.
4 |
5 | ## Deployment
6 |
7 | To deploy this sample into your own AWS account two seperate steps are required. The first is to deploy the infrastructure, the second to deploy the application code.
8 |
9 | ### Infrastructure
10 |
11 | The infrastructure is built using the AWS CDK. The deployed resources contain:
12 |
13 | - A VPC
14 | - 2 private, 2 public subnets
15 | - 2 NAT Gateways
16 | - Postgres RDS Instance
17 | - Secrets Manager secret for credentials
18 | - Lambda function for applying database changes
19 |
20 | To deploy the infrastructure, run the following commands in order:
21 |
22 | ```
23 | cd infrastructure/db-setup
24 | mvn clean install
25 | cd ../cdk
26 | cdk deploy
27 | ```
28 |
29 | This will compile the db-update Lambda function and then deploy the CDK infrastructure to your account. 3 values will output to the console that are required to deploy the application.
30 |
31 | ### Application
32 |
33 | The application is deployed using AWS SAM. You can [install AWS SAM here](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html). To deploy the application run the below commands:
34 |
35 | ```
36 | sam build
37 | sam deploy --guided
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/infrastructure/cdk/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath.txt
2 | target
3 | .classpath
4 | .project
5 | .idea
6 | .settings
7 | .vscode
8 | *.iml
9 |
10 | # CDK asset staging directory
11 | .cdk.staging
12 | cdk.out
13 |
14 |
--------------------------------------------------------------------------------
/infrastructure/cdk/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to your CDK Java project!
2 |
3 | This is a blank project for CDK development with Java.
4 |
5 | The `cdk.json` file tells the CDK Toolkit how to execute your app.
6 |
7 | It is a [Maven](https://maven.apache.org/) based project, so you can open this project with any Maven compatible Java IDE to build and run tests.
8 |
9 | ## Useful commands
10 |
11 | * `mvn package` compile and run tests
12 | * `cdk ls` list all stacks in the app
13 | * `cdk synth` emits the synthesized CloudFormation template
14 | * `cdk deploy` deploy this stack to your default AWS account/region
15 | * `cdk diff` compare deployed stack with current state
16 | * `cdk docs` open CDK documentation
17 |
18 | Enjoy!
19 |
--------------------------------------------------------------------------------
/infrastructure/cdk/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "mvn -e -q compile exec:java",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "target",
11 | "pom.xml",
12 | "src/test"
13 | ]
14 | },
15 | "context": {
16 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
17 | "@aws-cdk/core:stackRelativeExports": true,
18 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
19 | "@aws-cdk/aws-lambda:recognizeVersionProps": true,
20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
22 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
23 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
24 | "@aws-cdk/core:checkSecretUsage": true,
25 | "@aws-cdk/aws-iam:minimizePolicies": true,
26 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
27 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
28 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
29 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
30 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
31 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
32 | "@aws-cdk/core:enablePartitionLiterals": true,
33 | "@aws-cdk/core:target-partitions": [
34 | "aws",
35 | "aws-cn"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/infrastructure/cdk/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.product
7 | infrastructure
8 | 0.1
9 |
10 |
11 | UTF-8
12 | 2.50.0
13 | [10.0.0,11.0.0)
14 | 5.7.1
15 |
16 |
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-compiler-plugin
22 | 3.8.1
23 |
24 | 1.8
25 | 1.8
26 |
27 |
28 |
29 |
30 | org.codehaus.mojo
31 | exec-maven-plugin
32 | 3.0.0
33 |
34 | com.product.infrastructure.InfrastructureApp
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | software.amazon.awscdk
44 | aws-cdk-lib
45 | ${cdk.version}
46 |
47 |
48 |
49 | software.constructs
50 | constructs
51 | ${constructs.version}
52 |
53 |
54 |
55 | org.junit.jupiter
56 | junit-jupiter
57 | ${junit.version}
58 | test
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/infrastructure/cdk/src/main/java/com/product/infrastructure/DatabaseStack.java:
--------------------------------------------------------------------------------
1 | package com.product.infrastructure;
2 |
3 | import software.amazon.awscdk.Duration;
4 | import software.amazon.awscdk.services.ec2.*;
5 | import software.amazon.awscdk.services.lambda.Code;
6 | import software.amazon.awscdk.services.lambda.Function;
7 | import software.amazon.awscdk.services.lambda.Runtime;
8 | import software.amazon.awscdk.services.rds.*;
9 | import software.constructs.Construct;
10 |
11 | import java.util.ArrayList;
12 | import java.util.HashMap;
13 |
14 | public class DatabaseStack extends Construct{
15 | public DatabaseStack(final Construct construct, final String id, DatabaseStackProps stackProps){
16 | super(construct, id);
17 |
18 | DatabaseSecret dbSecret = createDatabaseSecret();
19 |
20 | ArrayList dbSecurityGroups = new ArrayList();
21 | dbSecurityGroups.add(createDatabaseSecurityGroup(stackProps.getVpc()));
22 |
23 | DatabaseInstance dbInstance = new DatabaseInstance(this, "product-api", DatabaseInstanceProps.builder()
24 | .vpc(stackProps.getVpc())
25 | .databaseName("productapi")
26 | .allowMajorVersionUpgrade(false)
27 | .backupRetention(Duration.days(0))
28 | .instanceIdentifier("productapi")
29 | .vpcSubnets(SubnetSelection.builder().subnetType(SubnetType.PRIVATE_WITH_EGRESS).build())
30 | .securityGroups(dbSecurityGroups)
31 | .engine(DatabaseInstanceEngine.postgres(PostgresInstanceEngineProps.builder().version(PostgresEngineVersion.VER_13_7).build()))
32 | .instanceType(InstanceType.of(InstanceClass.T4G, InstanceSize.MICRO))
33 | .credentials(Credentials.fromSecret(dbSecret))
34 | .build());
35 |
36 | dbInstance.getConnections().allowFromAnyIpv4(Port.tcp(5432));
37 | dbInstance.getConnections().allowFrom(dbInstance.getConnections().getSecurityGroups().get(0), Port.tcp(5432));
38 | dbInstance.getConnections().allowFrom(stackProps.getApplicationSecurityGroup(), Port.tcp(5432));
39 |
40 | createDbSetupLambdaFunction(stackProps, dbInstance, dbSecret);
41 |
42 | }
43 |
44 | private SecurityGroup createDatabaseSecurityGroup(IVpc vpc) {
45 | SecurityGroup databaseSecurityGroup = SecurityGroup.Builder.create(this, "DatabaseSG")
46 | .securityGroupName("DatabaseSG")
47 | .allowAllOutbound(false)
48 | .vpc(vpc)
49 | .build();
50 |
51 | databaseSecurityGroup.addIngressRule(
52 | Peer.ipv4(vpc.getVpcCidrBlock()),
53 | Port.tcp(5432),
54 | "Allow Database Traffic from local network");
55 |
56 | return databaseSecurityGroup;
57 | }
58 |
59 | private DatabaseSecret createDatabaseSecret() {
60 | return DatabaseSecret.Builder
61 | .create(this, "postgres")
62 | .secretName("product-api-db-secret")
63 | .username("postgres").build();
64 | }
65 |
66 | private Function createDbSetupLambdaFunction(DatabaseStackProps props, DatabaseInstance database, DatabaseSecret secret) {
67 | ArrayList securityGroups = new ArrayList();
68 | securityGroups.add(props.getApplicationSecurityGroup());
69 |
70 | Function function = Function.Builder.create(this, "DBSetupLambdaFunction")
71 | .runtime(Runtime.JAVA_11)
72 | .memorySize(512)
73 | .timeout(Duration.seconds(29))
74 | .code(Code.fromAsset("../db-setup/target/db-setup.jar"))
75 | .handler("com.amazon.aws.DBSetupHandler::handleRequest")
76 | .vpc(props.getVpc())
77 | .securityGroups(securityGroups)
78 | .environment(new HashMap() {{
79 | put("SECRET_NAME", secret.getSecretName());
80 | put("DB_CONNECTION_URL", "jdbc:postgresql://" + database.getDbInstanceEndpointAddress() + ":5432/productapi");
81 | put("DB_USER", "postgres");
82 | }})
83 | .build();
84 |
85 | secret.grantRead(function);
86 |
87 | return function;
88 | }
89 | }
90 |
91 |
--------------------------------------------------------------------------------
/infrastructure/cdk/src/main/java/com/product/infrastructure/DatabaseStackProps.java:
--------------------------------------------------------------------------------
1 | package com.product.infrastructure;
2 |
3 | import software.amazon.awscdk.services.ec2.ISecurityGroup;
4 | import software.amazon.awscdk.services.ec2.IVpc;
5 |
6 | public class DatabaseStackProps {
7 | static DatabaseStackProps props;
8 |
9 | private IVpc vpc;
10 | private ISecurityGroup applicationSecurityGroup;
11 |
12 | public static DatabaseStackProps builder() {
13 | props = new DatabaseStackProps();
14 |
15 | return props;
16 | }
17 |
18 | public DatabaseStackProps withVpc(IVpc vpc) {
19 | props.setVpc(vpc);
20 |
21 | return props;
22 | }
23 |
24 | public DatabaseStackProps withApplicationSecurityGroup(ISecurityGroup sg) {
25 | props.setApplicationSecurityGroup(sg);
26 |
27 | return props;
28 | }
29 |
30 | public DatabaseStackProps build() {
31 | return props;
32 | }
33 |
34 | public IVpc getVpc() {
35 | return vpc;
36 | }
37 |
38 | private void setVpc(IVpc vpc) {
39 | this.vpc = vpc;
40 | }
41 |
42 | public ISecurityGroup getApplicationSecurityGroup()
43 | {
44 | return applicationSecurityGroup;
45 | }
46 |
47 | private void setApplicationSecurityGroup(ISecurityGroup sg) {
48 | this.applicationSecurityGroup = sg;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/infrastructure/cdk/src/main/java/com/product/infrastructure/InfrastructureApp.java:
--------------------------------------------------------------------------------
1 | package com.product.infrastructure;
2 |
3 | import software.amazon.awscdk.App;
4 | import software.amazon.awscdk.StackProps;
5 |
6 | public class InfrastructureApp {
7 | public static void main(final String[] args) {
8 | App app = new App();
9 |
10 | new InfrastructureStack(app, "InfrastructureStack", StackProps.builder()
11 | // If you don't specify 'env', this stack will be environment-agnostic.
12 | // Account/Region-dependent features and context lookups will not work,
13 | // but a single synthesized template can be deployed anywhere.
14 |
15 | // Uncomment the next block to specialize this stack for the AWS Account
16 | // and Region that are implied by the current CLI configuration.
17 | /*
18 | .env(Environment.builder()
19 | .account(System.getenv("CDK_DEFAULT_ACCOUNT"))
20 | .region(System.getenv("CDK_DEFAULT_REGION"))
21 | .build())
22 | */
23 |
24 | // Uncomment the next block if you know exactly what Account and Region you
25 | // want to deploy the stack to.
26 | /*
27 | .env(Environment.builder()
28 | .account("123456789012")
29 | .region("us-east-1")
30 | .build())
31 | */
32 |
33 | // For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html
34 | .build());
35 |
36 | app.synth();
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/infrastructure/cdk/src/main/java/com/product/infrastructure/InfrastructureStack.java:
--------------------------------------------------------------------------------
1 | package com.product.infrastructure;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import software.amazon.awscdk.services.ec2.IpAddresses;
5 | import software.amazon.awscdk.services.ec2.Vpc;
6 | import software.amazon.awscdk.services.ec2.VpcAttributes;
7 | import software.amazon.awscdk.services.ec2.VpcProps;
8 | import software.constructs.Construct;
9 | import software.amazon.awscdk.Stack;
10 | import software.amazon.awscdk.StackProps;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class InfrastructureStack extends Stack {
16 | public InfrastructureStack(final Construct scope, final String id) {
17 | this(scope, id, null);
18 | }
19 |
20 | public InfrastructureStack(final Construct scope, final String id, final StackProps props) {
21 | super(scope, id, props);
22 |
23 | NetworkingStack networking = new NetworkingStack(this, "NetworkingStack");
24 |
25 | DatabaseStack databaseStack = new DatabaseStack(this, "DatabaseStack", DatabaseStackProps.builder()
26 | .withVpc(networking.getVpc())
27 | .withApplicationSecurityGroup(networking.getApplicationSecurityGroup())
28 | .build());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/infrastructure/cdk/src/main/java/com/product/infrastructure/NetworkingStack.java:
--------------------------------------------------------------------------------
1 | package com.product.infrastructure;
2 |
3 | import software.amazon.awscdk.CfnOutput;
4 | import software.amazon.awscdk.CfnOutputProps;
5 | import software.amazon.awscdk.services.ec2.*;
6 | import software.constructs.Construct;
7 |
8 | public class NetworkingStack extends Construct {
9 | private final Vpc vpc;
10 | private final ISecurityGroup applicationSecurityGroup;
11 |
12 | public NetworkingStack(final Construct scope, final String id){
13 | super(scope, id);
14 |
15 | vpc = new Vpc(this, "product-api-vpc", VpcProps.builder()
16 | .ipAddresses(IpAddresses.cidr("10.0.0.0/16"))
17 | .enableDnsHostnames(true)
18 | .enableDnsSupport(true)
19 | .maxAzs(3)
20 | .build());
21 |
22 | applicationSecurityGroup = new SecurityGroup(this, "ApplicationSecurityGroup",
23 | SecurityGroupProps
24 | .builder()
25 | .securityGroupName("applicationSG")
26 | .vpc(vpc)
27 | .allowAllOutbound(true)
28 | .build());
29 |
30 | CfnOutput appSgOutput = new CfnOutput(this, "app-sg-output", CfnOutputProps.builder()
31 | .exportName("ApplicationSecurityGroupId")
32 | .value(applicationSecurityGroup.getSecurityGroupId())
33 | .build());
34 |
35 | CfnOutput subnet1 = new CfnOutput(this, "app-subnet-1", CfnOutputProps.builder()
36 | .exportName("Subnet1Id")
37 | .value(vpc.getPrivateSubnets().get(0).getSubnetId())
38 | .build());
39 |
40 | CfnOutput subnet2 = new CfnOutput(this, "app-subnet-2", CfnOutputProps.builder()
41 | .exportName("Subnet2Id")
42 | .value(vpc.getPrivateSubnets().get(1).getSubnetId())
43 | .build());
44 | }
45 |
46 | public Vpc getVpc() {
47 | return vpc;
48 | }
49 |
50 | public ISecurityGroup getApplicationSecurityGroup() {
51 | return applicationSecurityGroup;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/infrastructure/cdk/src/test/java/com/product/infrastructure/InfrastructureTest.java:
--------------------------------------------------------------------------------
1 | // package com.myorg;
2 |
3 | // import software.amazon.awscdk.App;
4 | // import software.amazon.awscdk.assertions.Template;
5 | // import java.io.IOException;
6 |
7 | // import java.util.HashMap;
8 |
9 | // import org.junit.jupiter.api.Test;
10 |
11 | // example test. To run these tests, uncomment this file, along with the
12 | // example resource in java/src/main/java/com/myorg/InfrastructureStack.java
13 | // public class InfrastructureTest {
14 |
15 | // @Test
16 | // public void testStack() throws IOException {
17 | // App app = new App();
18 | // InfrastructureStack stack = new InfrastructureStack(app, "test");
19 |
20 | // Template template = Template.fromStack(stack);
21 |
22 | // template.hasResourceProperties("AWS::SQS::Queue", new HashMap() {{
23 | // put("VisibilityTimeout", 300);
24 | // }});
25 | // }
26 | // }
27 |
--------------------------------------------------------------------------------
/infrastructure/db-setup/dependency-reduced-pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.amazon.aws.example
5 | db-setup
6 | AWS Lambda function to setup the Amazon RDS Instance
7 | 1.0.0
8 |
9 |
10 |
11 | maven-shade-plugin
12 | 3.4.1
13 |
14 |
15 | package
16 |
17 | shade
18 |
19 |
20 |
21 |
22 | db-setup
23 |
24 |
25 |
26 | maven-compiler-plugin
27 | 3.10.1
28 |
29 | 11
30 | 11
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | software.amazon.awssdk
39 | bom
40 | ${aws.java.sdk.version}
41 | pom
42 | import
43 |
44 |
45 |
46 |
47 | 2.18.1
48 |
49 |
50 |
--------------------------------------------------------------------------------
/infrastructure/db-setup/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.amazon.aws.example
5 | db-setup
6 | 1.0.0
7 | jar
8 | AWS Lambda function to setup the Amazon RDS Instance
9 |
10 |
11 | 2.18.1
12 |
13 |
14 |
15 |
16 |
17 | software.amazon.awssdk
18 | bom
19 | ${aws.java.sdk.version}
20 | pom
21 | import
22 |
23 |
24 |
25 |
26 |
27 |
28 | org.postgresql
29 | postgresql
30 | 42.5.0
31 |
32 |
33 | commons-io
34 | commons-io
35 | 2.11.0
36 |
37 |
38 | com.amazonaws
39 | aws-lambda-java-core
40 | 1.2.1
41 |
42 |
43 | com.amazonaws
44 | aws-lambda-java-events
45 | 3.11.0
46 |
47 |
48 | software.amazon.lambda
49 | powertools-parameters
50 | 1.12.3
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | org.apache.maven.plugins
59 | maven-shade-plugin
60 | 3.4.1
61 |
62 | db-setup
63 |
64 |
65 |
66 | package
67 |
68 | shade
69 |
70 |
71 |
72 |
73 |
74 |
75 | org.apache.maven.plugins
76 | maven-compiler-plugin
77 | 3.10.1
78 |
79 | 11
80 | 11
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/infrastructure/db-setup/src/main/java/com/amazon/aws/AwsSecret.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws;
2 |
3 | public class AwsSecret {
4 | private String username;
5 | private String password;
6 | private String host;
7 | private String engine;
8 | private String port;
9 | private String dbInstanceIdentifier;
10 | private String dbname;
11 |
12 | public String getUsername() {
13 | return username;
14 | }
15 |
16 | public String getPassword() {
17 | return password;
18 | }
19 |
20 | public String getDbname() {
21 | return dbname;
22 | }
23 |
24 | public String getHost() {
25 | return host;
26 | }
27 |
28 | public String getEngine() {
29 | return engine;
30 | }
31 |
32 | public String getPort() {
33 | return port;
34 | }
35 |
36 | public String getDbInstanceIdentifier() {
37 | return dbInstanceIdentifier;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/infrastructure/db-setup/src/main/java/com/amazon/aws/DBSetupHandler.java:
--------------------------------------------------------------------------------
1 | package com.amazon.aws;
2 |
3 | import com.amazonaws.services.lambda.runtime.Context;
4 | import com.amazonaws.services.lambda.runtime.RequestHandler;
5 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
6 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
7 | import org.apache.commons.io.IOUtils;
8 | import software.amazon.lambda.powertools.parameters.ParamManager;
9 | import software.amazon.lambda.powertools.parameters.SecretsProvider;
10 | import software.amazon.lambda.powertools.parameters.transform.Transformer;
11 |
12 | import java.io.IOException;
13 | import java.sql.DriverManager;
14 | import java.sql.SQLException;
15 |
16 | public class DBSetupHandler implements RequestHandler {
17 | private static final String DB_CONNECTION = System.getenv("DB_CONNECTION_URL");
18 | private static final String DB_USER = System.getenv("DB_USER");
19 | private static final String SECRET_NAME = System.getenv("SECRET_NAME");
20 |
21 | private static SecretsProvider secretsProvider;
22 |
23 | public DBSetupHandler(){
24 | secretsProvider = ParamManager.getSecretsProvider();
25 | }
26 |
27 | public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
28 | AwsSecret secret = secretsProvider
29 | .withTransformation(Transformer.json)
30 | .get(SECRET_NAME, AwsSecret.class);
31 |
32 | try(var connection = DriverManager.getConnection(DB_CONNECTION, DB_USER, secret.getPassword())) {
33 | try(var statement = connection.createStatement()) {
34 | try(var sqlFile = getClass().getClassLoader().getResourceAsStream("setup.sql")) {
35 | statement.executeUpdate(IOUtils.toString(sqlFile));
36 | return new APIGatewayProxyResponseEvent()
37 | .withStatusCode(200)
38 | .withBody("DB Setup successful");
39 | }
40 | }
41 | } catch (SQLException | IOException sqlException) {
42 | context.getLogger().log("Error connection to the database:" + sqlException.getMessage());
43 | return new APIGatewayProxyResponseEvent()
44 | .withStatusCode(500)
45 | .withBody("Error initializing the database");
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/infrastructure/db-setup/src/main/resources/setup.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS products(id SERIAL PRIMARY KEY, name TEXT, price Numeric);
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.7.5
9 |
10 |
11 | com.product
12 | product-api
13 | 1.0.0
14 | product-api
15 | Product API
16 |
17 | 11
18 | UTF-8
19 | UTF-8
20 | 5.9.0
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-data-jpa
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-log4j2
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-web
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-validation
38 |
39 |
40 | javax.validation
41 | validation-api
42 | 2.0.1.Final
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-starter-data-jpa
47 |
48 |
49 | org.springframework.boot
50 | spring-boot-configuration-processor
51 | true
52 |
53 |
54 | org.postgresql
55 | postgresql
56 | 42.5.0
57 |
58 |
59 | software.amazon.awssdk
60 | eventbridge
61 | 2.17.282
62 |
63 |
64 | software.amazon.awssdk
65 | netty-nio-client
66 |
67 |
68 | software.amazon.awssdk
69 | apache-client
70 |
71 |
72 |
73 |
74 | software.amazon.awssdk
75 | aws-crt-client
76 | 2.17.282-PREVIEW
77 |
78 |
79 | software.amazon.awssdk
80 | secretsmanager
81 | 2.18.16
82 |
83 |
84 | software.amazon.awssdk
85 | ssm
86 | 2.18.16
87 |
88 |
89 | com.google.code.gson
90 | gson
91 |
92 |
93 | io.github.crac.com.amazonaws
94 | aws-lambda-java-runtime-interface-client
95 | 1.0.0
96 |
97 |
98 | com.amazonaws.serverless
99 | aws-serverless-java-container-springboot2
100 | 1.9
101 |
102 |
103 | software.amazon.lambda
104 | powertools-tracing
105 | 1.12.3
106 |
107 |
108 | software.amazon.lambda
109 | powertools-logging
110 | 1.12.3
111 |
112 |
113 | io.rest-assured
114 | rest-assured
115 | 5.2.0
116 | test
117 |
118 |
119 |
120 | org.apache.groovy
121 | groovy-xml
122 |
123 |
124 |
125 |
126 |
127 | io.rest-assured
128 | xml-path
129 | 5.2.0
130 | test
131 |
132 |
133 |
134 | org.mockito
135 | mockito-core
136 | 4.8.0
137 | test
138 |
139 |
140 | org.mockito
141 | mockito-junit-jupiter
142 | 4.8.0
143 | test
144 |
145 |
146 | org.mockito
147 | mockito-inline
148 | 4.8.0
149 | test
150 |
151 |
152 |
153 | org.junit.jupiter
154 | junit-jupiter-engine
155 | ${junit.jupiter.version}
156 | test
157 |
158 |
159 | org.junit.jupiter
160 | junit-jupiter-api
161 | ${junit.jupiter.version}
162 | test
163 |
164 |
165 | org.junit.jupiter
166 | junit-jupiter-params
167 | ${junit.jupiter.version}
168 | test
169 |
170 |
171 | uk.org.webcompere
172 | system-stubs-jupiter
173 | 1.1.0
174 | test
175 |
176 |
177 | software.amazon.awssdk
178 | cloudformation
179 | test
180 | 2.18.16
181 |
182 |
183 |
184 |
185 |
186 | org.apache.maven.plugins
187 | maven-shade-plugin
188 | 3.4.0
189 |
190 | false
191 |
192 |
193 |
194 | package
195 |
196 | shade
197 |
198 |
199 |
200 |
201 | org.apache.tomcat.embed:*
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 | org.apache.maven.plugins
211 | maven-surefire-plugin
212 | 2.22.2
213 |
214 |
218 |
219 | **/*IT.java
220 |
221 |
222 |
223 |
224 |
225 |
--------------------------------------------------------------------------------
/src/main/java/com/product/api/ApplicationConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.product.api;
2 | import com.google.gson.Gson;
3 | import org.springframework.context.annotation.Configuration;
4 | import software.amazon.awssdk.services.ssm.SsmClient;
5 | import software.amazon.awssdk.services.ssm.model.GetParameterRequest;
6 | import software.amazon.awssdk.services.ssm.model.GetParameterResponse;
7 |
8 | @Configuration
9 | public class ApplicationConfiguration {
10 | private final Gson gson = new Gson();
11 | private ApplicationProperties properties;
12 |
13 | public ApplicationProperties getApplicationProperties()
14 | {
15 | if (properties != null)
16 | {
17 | return properties;
18 | }
19 |
20 | this.properties = getProps();
21 |
22 | return properties;
23 | }
24 |
25 | private ApplicationProperties getProps() {
26 | var parameterName = System.getenv("CONFIG_PARAMETER_NAME");
27 | if (parameterName == null || parameterName == "") {
28 | return new ApplicationProperties();
29 | }
30 |
31 | var ssmClient = SsmClient.create();
32 |
33 | String parameter;
34 |
35 | var getParameterRequest = GetParameterRequest.builder()
36 | .name(parameterName)
37 | .build();
38 |
39 | GetParameterResponse result = null;
40 |
41 | try {
42 | result = ssmClient.getParameter(getParameterRequest);
43 | }
44 | catch (Exception e) {
45 | throw e;
46 | }
47 | if (result.parameter().value() != null) {
48 | parameter = result.parameter().value();
49 | return gson.fromJson(parameter, ApplicationProperties.class);
50 | }
51 |
52 | return null;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/product/api/ApplicationProperties.java:
--------------------------------------------------------------------------------
1 | package com.product.api;
2 |
3 | public class ApplicationProperties {
4 | private String databaseEndpoint;
5 |
6 | public String getDatabaseEndpoint()
7 | {
8 | return databaseEndpoint;
9 | }
10 |
11 | public void setMyApplicationProperty(String propertyValue)
12 | {
13 | this.databaseEndpoint = propertyValue;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/product/api/AwsSecret.java:
--------------------------------------------------------------------------------
1 | package com.product.api;
2 |
3 | public class AwsSecret {
4 | private String username;
5 | private String password;
6 | private String host;
7 | private String engine;
8 | private String port;
9 | private String dbInstanceIdentifier;
10 |
11 | public String getUsername() {
12 | return username;
13 | }
14 |
15 | public String getPassword() {
16 | return password;
17 | }
18 |
19 | public String getHost() {
20 | return host;
21 | }
22 |
23 | public String getEngine() {
24 | return engine;
25 | }
26 |
27 | public String getPort() {
28 | return port;
29 | }
30 |
31 | public String getDbInstanceIdentifier() {
32 | return dbInstanceIdentifier;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/product/api/JpaConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.product.api;
2 | import com.google.gson.Gson;
3 | import org.crac.Core;
4 | import org.springframework.boot.jdbc.DataSourceBuilder;
5 |
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.jdbc.datasource.SimpleDriverDataSource;
9 | import org.springframework.orm.jpa.JpaTransactionManager;
10 | import org.springframework.orm.jpa.JpaVendorAdapter;
11 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
12 | import org.springframework.orm.jpa.vendor.Database;
13 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
14 | import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
15 | import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
16 | import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
17 |
18 | import javax.persistence.EntityManagerFactory;
19 | import javax.sql.DataSource;
20 |
21 | @Configuration
22 | public class JpaConfiguration {
23 | private final Gson gson = new Gson();
24 | public JpaConfiguration()
25 | {
26 | }
27 |
28 | public DataSource dataSource() {
29 | final AwsSecret dbCredentials = getSecret();
30 |
31 | var dataSource = new SimpleDriverDataSource();
32 | dataSource.setDriverClass(org.postgresql.Driver.class);
33 | dataSource.setUrl("jdbc:postgresql://" + System.getenv("DATABASE_ENDPOINT") + ":" + dbCredentials.getPort() + "/productapi");
34 | dataSource.setUsername(dbCredentials.getUsername());
35 | dataSource.setPassword(dbCredentials.getPassword());
36 |
37 | return dataSource;
38 | }
39 |
40 | private AwsSecret getSecret() {
41 | var secretsManagerClient = SecretsManagerClient.create();
42 |
43 | String secret;
44 |
45 | var getSecretValueRequest = GetSecretValueRequest.builder()
46 | .secretId(System.getenv("SECRET_NAME"))
47 | .build();
48 |
49 | GetSecretValueResponse result = null;
50 |
51 | try {
52 | result = secretsManagerClient.getSecretValue(getSecretValueRequest);
53 | }
54 | catch (Exception e) {
55 | throw e;
56 | }
57 | if (result.secretString() != null) {
58 | secret = result.secretString();
59 | return gson.fromJson(secret, AwsSecret.class);
60 | }
61 |
62 | return null;
63 | }
64 |
65 | @Bean
66 | public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
67 | return new JpaTransactionManager(emf);
68 | }
69 |
70 | @Bean
71 | public JpaVendorAdapter jpaVendorAdapter() {
72 | HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
73 | jpaVendorAdapter.setDatabase(Database.POSTGRESQL);
74 | jpaVendorAdapter.setGenerateDdl(true);
75 |
76 | return jpaVendorAdapter;
77 | }
78 |
79 | @Bean
80 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
81 | LocalContainerEntityManagerFactoryBean lemfb = new LocalContainerEntityManagerFactoryBean();
82 | lemfb.setDataSource(dataSource());
83 | lemfb.setJpaVendorAdapter(jpaVendorAdapter());
84 | lemfb.setPackagesToScan("com.product.api");
85 | return lemfb;
86 | }
87 | }
--------------------------------------------------------------------------------
/src/main/java/com/product/api/ProductApiApplication.java:
--------------------------------------------------------------------------------
1 | package com.product.api;
2 |
3 | import org.apache.logging.log4j.LogManager;
4 | import org.apache.logging.log4j.Logger;
5 | import org.springframework.boot.SpringApplication;
6 | import com.fasterxml.jackson.databind.DeserializationFeature;
7 | import com.fasterxml.jackson.databind.ObjectMapper;
8 | import org.springframework.boot.autoconfigure.SpringBootApplication;
9 | import org.springframework.context.annotation.Bean;
10 |
11 | @SpringBootApplication()
12 | public class ProductApiApplication {
13 | private static final Logger LOG = LogManager.getLogger();
14 |
15 | public static void main(String[] args) {
16 | LOG.info("Application startup");
17 |
18 | SpringApplication.run(ProductApiApplication.class, args);
19 | }
20 |
21 | @Bean
22 | public ObjectMapper getObjectMapper() {
23 | ObjectMapper objectMapper = new ObjectMapper();
24 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
25 | return objectMapper;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/product/api/ProductController.java:
--------------------------------------------------------------------------------
1 | package com.product.api;
2 |
3 | import com.product.api.core.ProductDTO;
4 | import com.product.api.core.ProductService;
5 | import org.apache.logging.log4j.LogManager;
6 | import org.apache.logging.log4j.Logger;
7 | import org.springframework.http.ResponseEntity;
8 | import org.springframework.web.bind.annotation.*;
9 |
10 | import javax.validation.Valid;
11 |
12 | @RestController
13 | public class ProductController {
14 | private final ProductService productService;
15 | private final ApplicationProperties applicationProperties;
16 |
17 | private static final Logger LOG = LogManager.getLogger();
18 |
19 | public ProductController(ProductService productService, ApplicationConfiguration configuration) {
20 | this.productService = productService;
21 | this.applicationProperties = configuration.getApplicationProperties();
22 | }
23 |
24 | @PostMapping("/product")
25 | ResponseEntity addProduct(@Valid @RequestBody ProductDTO product) {
26 | var createdProduct = productService.CreateProduct(product);
27 |
28 | return ResponseEntity.ok(createdProduct);
29 | }
30 |
31 | @GetMapping("/product")
32 | ResponseEntity> listProducts() {
33 | LOG.info("Received request to retrieve products");
34 |
35 | var productList = productService.ListProducts();
36 |
37 | LOG.info("Product listing successful");
38 |
39 | return ResponseEntity.ok(productList);
40 | }
41 |
42 | @GetMapping("/product/{id}")
43 | ResponseEntity getProduct(@PathVariable long id) {
44 | var product = productService.GetProduct(id);
45 |
46 | return ResponseEntity.ok(product);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/product/api/SnapshotConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.product.api;
2 |
3 | import org.apache.logging.log4j.LogManager;
4 | import org.apache.logging.log4j.Logger;
5 | import org.crac.Context;
6 | import org.crac.Core;
7 | import org.crac.Resource;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
11 | import org.springframework.stereotype.Component;
12 |
13 | import javax.annotation.PostConstruct;
14 | import javax.sql.DataSource;
15 | import java.sql.Connection;
16 | import java.sql.DriverManager;
17 |
18 | @Configuration
19 | public class SnapshotConfiguration implements Resource {
20 | private static final Logger LOG = LogManager.getLogger();
21 | LocalContainerEntityManagerFactoryBean _dataSourceBean;
22 |
23 | public SnapshotConfiguration(LocalContainerEntityManagerFactoryBean dataSourceBean)
24 | {
25 | LOG.info("Snapshot config constructor");
26 |
27 | Core.getGlobalContext().register(SnapshotConfiguration.this);
28 |
29 | _dataSourceBean = dataSourceBean;
30 | }
31 |
32 | @Override
33 | public void beforeCheckpoint(Context extends Resource> context) throws Exception {
34 | LOG.info("Before checkpoint");
35 | DataSource dataSource = _dataSourceBean.getDataSource();
36 | Connection databaseConnection = dataSource.getConnection();
37 |
38 | if (!databaseConnection.isClosed())
39 | {
40 | LOG.info("Closing connection");
41 | databaseConnection.close();
42 | }
43 | }
44 |
45 | @Override
46 | public void afterRestore(Context extends Resource> context) throws Exception {
47 | LOG.info("Restoring");
48 | }
49 | }
--------------------------------------------------------------------------------
/src/main/java/com/product/api/StreamLambdaHandler.java:
--------------------------------------------------------------------------------
1 | package com.product.api;
2 |
3 | import com.amazonaws.serverless.exceptions.ContainerInitializationException;
4 | import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
5 | import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
6 | import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
7 | import com.amazonaws.services.lambda.runtime.Context;
8 | import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
9 |
10 | import java.io.IOException;
11 | import java.io.InputStream;
12 | import java.io.OutputStream;
13 |
14 | public class StreamLambdaHandler implements RequestStreamHandler {
15 | private static SpringBootLambdaContainerHandler handler;
16 | static {
17 | try {
18 | handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(ProductApiApplication.class);
19 | // If you are using HTTP APIs with the version 2.0 of the proxy model, use the getHttpApiV2ProxyHandler
20 | // method: handler = SpringBootLambdaContainerHandler.getHttpApiV2ProxyHandler(Application.class);
21 | } catch (ContainerInitializationException e) {
22 | // if we fail here. We re-throw the exception to force another cold start
23 | e.printStackTrace();
24 | throw new RuntimeException("Could not initialize Spring Boot application", e);
25 | }
26 | }
27 |
28 | @Override
29 | public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
30 | throws IOException {
31 | handler.proxyStream(inputStream, outputStream, context);
32 | }
33 | }
--------------------------------------------------------------------------------
/src/main/java/com/product/api/core/IProductRepository.java:
--------------------------------------------------------------------------------
1 | package com.product.api.core;
2 |
3 | import org.springframework.data.repository.CrudRepository;
4 |
5 | public interface IProductRepository extends CrudRepository {
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/product/api/core/Product.java:
--------------------------------------------------------------------------------
1 | package com.product.api.core;
2 |
3 | import javax.persistence.*;
4 |
5 | @Entity
6 | @Table(name = "products")
7 | public class Product {
8 | public Product()
9 | {
10 | }
11 |
12 | @Id
13 | @GeneratedValue(strategy = GenerationType.IDENTITY)
14 | private long id;
15 | private String name;
16 | private Double price;
17 |
18 | public long getId() {
19 | return id;
20 | }
21 |
22 | public void setId(long id) {
23 | this.id = id;
24 | }
25 |
26 | public String getName() {
27 | return name;
28 | }
29 |
30 | public void setName(String name) {
31 | this.name = name;
32 | }
33 |
34 | public Double getPrice() {
35 | return price;
36 | }
37 |
38 | public void setPrice(Double price) {
39 | this.price = price;
40 | }
41 |
42 | public ProductDTO asDto()
43 | {
44 | ProductDTO dto = new ProductDTO();
45 | dto.setId(this.id);
46 | dto.setName(this.name);
47 | dto.setPrice(this.price);
48 |
49 | return dto;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/product/api/core/ProductDTO.java:
--------------------------------------------------------------------------------
1 | package com.product.api.core;
2 |
3 | import javax.validation.constraints.Min;
4 | import javax.validation.constraints.NotBlank;
5 | import javax.validation.constraints.NotNull;
6 |
7 | public class ProductDTO {
8 | private long id;
9 |
10 | @NotBlank(message="Name cannot be blank")
11 | private String name;
12 |
13 | @Min(value = 0, message = "Price must be greater than 0")
14 | @NotNull
15 | private Double price;
16 |
17 | public long getId() {
18 | return id;
19 | }
20 |
21 | public void setId(long id) {
22 | this.id = id;
23 | }
24 |
25 | public String getName() {
26 | return name;
27 | }
28 |
29 | public void setName(String name) {
30 | this.name = name;
31 | }
32 |
33 | public Double getPrice() {
34 | return price;
35 | }
36 |
37 | public void setPrice(Double price) {
38 | this.price = price;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/product/api/core/ProductService.java:
--------------------------------------------------------------------------------
1 | package com.product.api.core;
2 |
3 | import org.springframework.stereotype.Service;
4 |
5 | import java.util.ArrayList;
6 |
7 | @Service
8 | public class ProductService {
9 | private final IProductRepository productRepository;
10 |
11 | public ProductService(IProductRepository productRepository){
12 | this.productRepository = productRepository;
13 | }
14 |
15 | public ProductDTO CreateProduct(ProductDTO productDTO) {
16 | var product = new Product();
17 | product.setName(productDTO.getName());
18 | product.setPrice(productDTO.getPrice());
19 |
20 | var savedProduct = this.productRepository.save(product);
21 |
22 | return savedProduct.asDto();
23 | }
24 |
25 | public ProductDTO GetProduct(long productId) {
26 | var retrievedProduct = this.productRepository.findById(productId);
27 |
28 | return retrievedProduct.map(Product::asDto).orElse(null);
29 |
30 | }
31 |
32 | public Iterable ListProducts() {
33 | var products = this.productRepository.findAll();
34 |
35 | var productDtoList = new ArrayList();
36 |
37 | var productIterator = products.iterator();
38 |
39 | while (productIterator.hasNext())
40 | {
41 | productDtoList.add(productIterator.next().asDto());
42 | }
43 |
44 | return productDtoList;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.main.banner-mode=off
2 |
3 | spring.jpa.hibernate.ddl-auto=none
4 |
5 | spring.datasource.url=jdbc:postgresql://localhost:5432/products
6 | spring.datasource.username=postgres
7 | spring.datasource.password=mysecretpassword
8 |
9 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
--------------------------------------------------------------------------------
/src/main/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/resources/schema-postgres.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS products;
2 | CREATE TABLE products(id serial PRIMARY KEY, name VARCHAR(255), price decimal);
--------------------------------------------------------------------------------
/src/test/java/ProductApiIT.java:
--------------------------------------------------------------------------------
1 | import io.restassured.RestAssured;
2 | import io.restassured.response.Response;
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 |
6 | public class ProductApiIT {
7 | @Test
8 | public void testGetProducts_ShouldReturnProductsArray()
9 | {
10 | Response response = RestAssured.given()
11 | .header("Content-Type", "application/json")
12 | .header("Accept", "application/json")
13 | .when()
14 | .get("https://icmayost93.execute-api.us-east-1.amazonaws.com/Prod/product");
15 |
16 | Assertions.assertEquals(response.statusCode(), 200);
17 | String responseBody = response.asString();
18 | Assertions.assertNotNull(responseBody);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/template.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Transform: AWS::Serverless-2016-10-31
3 |
4 | Globals:
5 | Function:
6 | Timeout: 30
7 | Environment:
8 | Variables:
9 | POWERTOOLS_SERVICE_NAME: ProductApi
10 | SECRET_NAME: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${DatabaseSecretName}
11 | DATABASE_ENDPOINT: !Ref DatabaseEndpoint
12 |
13 | Parameters:
14 | SecurityGroupId:
15 | Description: Security Group for the application
16 | Type: String
17 | Subnet1Id:
18 | Description: Subnet Id for the first subnet
19 | Type: String
20 | Subnet2Id:
21 | Description: Subnet Id for the second subnet
22 | Type: String
23 | DatabaseSecretName:
24 | Description: The name of the secret holding database credentials
25 | Type: String
26 | DatabaseEndpoint:
27 | Description: The endpoint of the database
28 | Type: String
29 |
30 | Resources:
31 | ProductApiFunction:
32 | Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
33 | Properties:
34 | CodeUri: .
35 | Handler: com.product.api.StreamLambdaHandler::handleRequest
36 | Runtime: java11
37 | AutoPublishAlias: production
38 | SnapStart:
39 | ApplyOn: PublishedVersions
40 | VpcConfig:
41 | SecurityGroupIds:
42 | - !Ref SecurityGroupId
43 | SubnetIds:
44 | - !Ref Subnet1Id
45 | - !Ref Subnet2Id
46 | Architectures:
47 | - x86_64
48 | MemorySize: 2048
49 | Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
50 | Variables:
51 | JAVA_TOOL_OPTIONS: -XX:+TieredCompilation -XX:TieredStopAtLevel=1 # More info about tiered compilation https://aws.amazon.com/blogs/compute/optimizing-aws-lambda-function-performance-for-java/
52 | Policies:
53 | - AWSSecretsManagerGetSecretValuePolicy:
54 | SecretArn: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${DatabaseSecretName}
55 | Events:
56 | HelloWorld:
57 | Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
58 | Properties:
59 | Path: /{proxy+}
60 | Method: ANY
61 |
--------------------------------------------------------------------------------