Skip to content

Commit 9273614

Browse files
authored
Merge pull request #1483 from ravikirang28/ravikirang28-feature-apigw-kinesis-lambda-ddb
New Pattern Submission - API GW to Kinesis to Lambda to DDB
2 parents 22b56eb + 9a6f416 commit 9273614

File tree

8 files changed

+599
-0
lines changed

8 files changed

+599
-0
lines changed

apigw-kinesis-lambda-ddb/README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Amazon API Gateway to Amazon Kinesis Data Stream to AWS Lambda to Amazon DynamoDB
2+
3+
This pattern explains how to deploy a SAM application with Amazon API Gateway, Amazon Kinesis Data Stream, AWS Lambda, and Amazon DynamoDB. When an HTTP POST request is made to the Amazon API Gateway endpoint, Gateway authorizes the request by checking Basic auth credentials and on valid credentials, request payload is sent to Amazon Kinesis Data Stream. This pattern is especially useful in cases of large payloads since Kinesis Data Stream can support upto 1MB paylod size. AWS Lambda function consumes event from the Data Stream and inserts the event/payload into the Amazon DynamoDB table. Amazon Lambda is also configured with a Dead Letter Queue where events are sent when retries to process those messages are repeatedly failed.
4+
5+
Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.
6+
7+
## Requirements
8+
9+
* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
10+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
11+
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
12+
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed
13+
14+
## Deployment Instructions
15+
16+
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
17+
```
18+
git clone https://github.com/aws-samples/serverless-patterns
19+
```
20+
1. Change directory to the pattern directory:
21+
```
22+
cd apigw-kinesis-lambda-ddb
23+
```
24+
1. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file:
25+
```
26+
sam deploy --guided
27+
```
28+
1. During the prompts:
29+
* Enter a stack name
30+
* Enter the desired AWS Region
31+
* Allow SAM CLI to create IAM roles with the required permissions.
32+
33+
Once you have run `sam deploy -guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults.
34+
35+
1. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing.
36+
37+
## How it works
38+
39+
<img src="docs/apigw-kinesis-lambda-ddb.drawio.png" alt="architecture diagram"/>
40+
41+
- This pattern deploys an Amazon API Gateway HTTP API with route /submit/{streamName}/{eventId} configured with basic authentication.
42+
- On receiving a request, API Gateway will invoke a Lambda authorizer which validates the request and returns a policy informing API Gateway to accept or deny the request.
43+
- When request is accepted, API Gateway sends the message payload to Kinesis Data Stream.
44+
- Messages from Kinesis Data Streams is posted to a lambda function to process them. Lambda uses a SQS queue as Dead Letter Queue to send the messages in case of continued failures to process the messages.
45+
- Lambda saves the received messages into a DynamoDB table.
46+
47+
## Testing
48+
49+
Once the application is deployed:
50+
- Retrieve the HttpApiEndpoint value from CloudFormation Outputs
51+
- Retrieve the username and password from Secrets Manager in AWS Console.
52+
- Invoke the endpoint from Postman using some json payload and verify the payload saved in DynamoDB.
53+
54+
Request:
55+
- Request URL: https://{RestApiEndpoint}.execute-api.{Region}.amazonaws.com/{gatewayStage}/submit/{streamName}/{eventId}
56+
- Region - Name of AWS Resion, Example: us-east-1
57+
- gatewayStage - Name of the API Gateway Stage. A stage is a named reference to a deployment, which is a snapshot of the API. "prod" is the stage name used in the SAM template.
58+
- streamName - This is the name of kinesis stream created i.e., GatewayEventsStream
59+
- eventId - Value in this attribute is used to choose PartitionKey in Kinesis stream. This example uses a single shard but when multiple shards are used, this eventId should be unique to share the load with multiple shards. Example: 55ad376f-86bf-4b06-9d3a-23237464dbd4
60+
- Request Method: POST
61+
- Request Header: "Content-Type: application/json"
62+
- Request Header: "Authorization: Basic <credentials>" (where credentials is the Base64 encoding of ID and password joined by a single colon :)
63+
- Request Body: {"eventId":"value1", "message":"event message for testing"} (This could be any JSON payload)
64+
65+
Example URL: https://abc1234def.execute-api.us-east-1.amazonaws.com/prod/submit/GatewayEventsStream/55ad376f-86bf-4b06-9d3a-23237464dbd4
66+
67+
## Cleanup
68+
69+
1. Delete the stack
70+
```bash
71+
sam delete
72+
```
73+
This pattern was contributed by Ravi Kiran Ganji.
74+
75+
----
76+
Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
77+
78+
SPDX-License-Identifier: MIT-0

apigw-kinesis-lambda-ddb/api.yaml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
openapi: "3.0.1"
2+
info:
3+
title: "Rest Api"
4+
version: "2023-06-25 17:32:29UTC"
5+
paths:
6+
/submit/{streamName}/{eventId}:
7+
post:
8+
parameters:
9+
- in: path
10+
name: streamName
11+
schema:
12+
type: string
13+
required: true
14+
- in: path
15+
name: eventId
16+
schema:
17+
type: string
18+
required: true
19+
responses:
20+
200:
21+
description: "Response for POST /"
22+
content:
23+
application/json:
24+
schema:
25+
type: object
26+
properties:
27+
SequenceNumber:
28+
type: string
29+
ShardId:
30+
type: string
31+
x-amazon-apigateway-integration:
32+
type: aws
33+
credentials:
34+
Fn::GetAtt: [ApiGatewayRole, Arn]
35+
uri: arn:aws:apigateway:us-east-1:kinesis:action/PutRecord
36+
responses:
37+
default:
38+
statusCode: "200"
39+
requestParameters:
40+
integration.request.header.Content-Type: "'application/x-amz-json-1.1'"
41+
requestTemplates:
42+
application/json: |-
43+
{
44+
"StreamName": "$input.params('streamName')",
45+
"Data": "$util.base64Encode($input.json('$'))",
46+
"PartitionKey": "$input.params('eventId')"
47+
}
48+
passthroughBehavior: NEVER
49+
httpMethod: POST
50+
51+
x-amazon-apigateway-cors:
52+
allowMethods:
53+
- "*"
54+
maxAge: 0
55+
allowCredentials: false
56+
allowOrigins:
57+
- "*"
58+
x-amazon-apigateway-importexport-version: "1.0"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<mxfile host="Electron" modified="2023-07-05T13:01:11.047Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/21.2.8 Chrome/112.0.5615.165 Electron/24.2.0 Safari/537.36" etag="fmjTFKlHViZRU2KFigbw" version="21.2.8" type="device">
2+
<diagram name="Pattern" id="9mZGnsqC8aDAnPaW2TS9">
3+
<mxGraphModel dx="1114" dy="822" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
4+
<root>
5+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-0" />
6+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-1" parent="ScqvUdBJJHNrSUMA6TMX-0" />
7+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-2" value="" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;dashPattern=12 12;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
8+
<mxGeometry x="194" y="650" width="130" height="70" as="geometry" />
9+
</mxCell>
10+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="ScqvUdBJJHNrSUMA6TMX-1" source="ScqvUdBJJHNrSUMA6TMX-16" target="ScqvUdBJJHNrSUMA6TMX-26" edge="1">
11+
<mxGeometry relative="1" as="geometry" />
12+
</mxCell>
13+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-16" value="Kinesis&lt;br&gt;Data Streams" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;gradientColor=#945DF2;gradientDirection=north;fillColor=#5A30B5;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.kinesis_data_streams;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
14+
<mxGeometry x="421" y="490" width="78" height="78" as="geometry" />
15+
</mxCell>
16+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="ScqvUdBJJHNrSUMA6TMX-1" source="ScqvUdBJJHNrSUMA6TMX-20" target="ScqvUdBJJHNrSUMA6TMX-23" edge="1">
17+
<mxGeometry relative="1" as="geometry" />
18+
</mxCell>
19+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-20" value="Client" style="verticalLabelPosition=bottom;html=1;verticalAlign=top;strokeWidth=1;align=center;outlineConnect=0;dashed=0;outlineConnect=0;shape=mxgraph.aws3d.client;aspect=fixed;strokeColor=none;fillColor=#777777;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
20+
<mxGeometry x="50" y="477" width="60" height="104" as="geometry" />
21+
</mxCell>
22+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="ScqvUdBJJHNrSUMA6TMX-1" source="ScqvUdBJJHNrSUMA6TMX-23" target="ScqvUdBJJHNrSUMA6TMX-16" edge="1">
23+
<mxGeometry relative="1" as="geometry" />
24+
</mxCell>
25+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="ScqvUdBJJHNrSUMA6TMX-1" source="ScqvUdBJJHNrSUMA6TMX-23" target="ScqvUdBJJHNrSUMA6TMX-2" edge="1">
26+
<mxGeometry relative="1" as="geometry" />
27+
</mxCell>
28+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-23" value="API Gateway" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;gradientColor=#FF4F8B;gradientDirection=north;fillColor=#BC1356;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.api_gateway;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
29+
<mxGeometry x="220" y="490" width="78" height="78" as="geometry" />
30+
</mxCell>
31+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="ScqvUdBJJHNrSUMA6TMX-1" source="ScqvUdBJJHNrSUMA6TMX-26" edge="1">
32+
<mxGeometry relative="1" as="geometry">
33+
<mxPoint x="830" y="529" as="targetPoint" />
34+
</mxGeometry>
35+
</mxCell>
36+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;dashed=1;dashPattern=12 12;" parent="ScqvUdBJJHNrSUMA6TMX-1" source="ScqvUdBJJHNrSUMA6TMX-26" target="ScqvUdBJJHNrSUMA6TMX-27" edge="1">
37+
<mxGeometry relative="1" as="geometry" />
38+
</mxCell>
39+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-26" value="Lambda" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;gradientColor=#F78E04;gradientDirection=north;fillColor=#D05C17;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.lambda;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
40+
<mxGeometry x="620" y="490" width="78" height="78" as="geometry" />
41+
</mxCell>
42+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-27" value="DLQ" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;gradientColor=#FF4F8B;gradientDirection=north;fillColor=#BC1356;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.sqs;labelPosition=center;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
43+
<mxGeometry x="620" y="650" width="78" height="78" as="geometry" />
44+
</mxCell>
45+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-34" value="Authorizer" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;gradientColor=#F78E04;gradientDirection=north;fillColor=#D05C17;strokeColor=#ffffff;dashed=0;verticalLabelPosition=middle;verticalAlign=middle;align=left;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.lambda;labelPosition=right;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
46+
<mxGeometry x="206" y="660" width="50" height="50" as="geometry" />
47+
</mxCell>
48+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-44" value="1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;strokeWidth=2;fontFamily=Tahoma;spacingBottom=4;spacingRight=2;strokeColor=#d3d3d3;fontColor=#FFFFFF;labelBackgroundColor=none;fillColor=#000000;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
49+
<mxGeometry x="149" y="505" width="20" height="20" as="geometry" />
50+
</mxCell>
51+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-45" value="2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;strokeWidth=2;fontFamily=Tahoma;spacingBottom=4;spacingRight=2;strokeColor=#d3d3d3;fontColor=#FFFFFF;labelBackgroundColor=none;fillColor=#000000;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
52+
<mxGeometry x="261" y="595" width="20" height="20" as="geometry" />
53+
</mxCell>
54+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-46" value="3" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;strokeWidth=2;fontFamily=Tahoma;spacingBottom=4;spacingRight=2;strokeColor=#d3d3d3;fontColor=#FFFFFF;labelBackgroundColor=none;fillColor=#000000;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
55+
<mxGeometry x="349" y="505" width="20" height="20" as="geometry" />
56+
</mxCell>
57+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-47" value="4" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;strokeWidth=2;fontFamily=Tahoma;spacingBottom=4;spacingRight=2;strokeColor=#d3d3d3;fontColor=#FFFFFF;labelBackgroundColor=none;fillColor=#000000;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
58+
<mxGeometry x="549" y="505" width="20" height="20" as="geometry" />
59+
</mxCell>
60+
<mxCell id="ScqvUdBJJHNrSUMA6TMX-48" value="5" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;strokeWidth=2;fontFamily=Tahoma;spacingBottom=4;spacingRight=2;strokeColor=#d3d3d3;fontColor=#FFFFFF;labelBackgroundColor=none;fillColor=#000000;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
61+
<mxGeometry x="749" y="505" width="20" height="20" as="geometry" />
62+
</mxCell>
63+
<mxCell id="MM6g9GSgHMZVfPPfBSF2-0" value="DynamoDB&lt;br&gt;(Provisioned)" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;gradientColor=#4D72F3;gradientDirection=north;fillColor=#3334B9;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.dynamodb;" parent="ScqvUdBJJHNrSUMA6TMX-1" vertex="1">
64+
<mxGeometry x="830" y="490" width="78" height="78" as="geometry" />
65+
</mxCell>
66+
</root>
67+
</mxGraphModel>
68+
</diagram>
69+
</mxfile>
64 KB
Loading
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"title": "Amazon API Gateway to Amazon Kinesis Data Stream to AWS Lambda to Amazon DynamoDB",
3+
"description": "This pattern explains how to deploy a SAM application with Amazon API Gateway, Amazon Kinesis Data Stream, AWS Lambda, and Amazon DynamoDB. When an HTTP POST request is made to the Amazon API Gateway endpoint, Gateway authorizes the request by checking Basic auth credentials and on valid credentials, request payload is sent to Amazon Kinesis Data Stream. This pattern is especially useful in cases of large payloads since Kinesis Data Stream can support upto 1MB paylod size. AWS Lambda function consumes event from the Data Stream and inserts the event/payload into the Amazon DynamoDB table. Amazon Lambda is also configured with a Dead Letter Queue where events are sent when retries to process those messages are repeatedly failed.",
4+
"language": "Python",
5+
"level": "300",
6+
"framework": "SAM",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"This pattern deploys an Amazon API Gateway HTTP API with route /submit/{streamName}/{eventId} configured with basic authentication.",
11+
"On receiving a request, API Gateway will invoke a Lambda authorizer which validates the request and returns a policy informing API Gateway to accept or deny the request.",
12+
"When request is accepted, API Gateway sends the message payload to Kinesis Data Stream.",
13+
"Messages from Kinesis Data Streams is posted to a lambda function to process them. Lambda uses a SQS queue as Dead Letter Queue to send the messages in case of continued failures to process the messages.",
14+
"Lambda saves the received messages into a DynamoDB table."
15+
]
16+
},
17+
"gitHub": {
18+
"template": {
19+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/apigw-kinesis-lambda-ddb",
20+
"templateURL": "serverless-patterns/apigw-kinesis-lambda-ddb",
21+
"projectFolder": "apigw-kinesis-lambda-ddb",
22+
"templateFile": "apigw-kinesis-lambda-ddb/template.yaml"
23+
}
24+
},
25+
"resources": {
26+
"bullets": [
27+
{
28+
"text": "Lambda Authorizers",
29+
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html"
30+
},
31+
{
32+
"text": "Dead Letter Queues",
33+
"link": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-deadletterqueue.html"
34+
},
35+
{
36+
"text": "Amazon Kinesis Data Streams",
37+
"link": "https://docs.aws.amazon.com/streams/latest/dev/introduction.html"
38+
}
39+
]
40+
},
41+
"deploy": {
42+
"text": [
43+
"sam deploy"
44+
]
45+
},
46+
"testing": {
47+
"text": [
48+
"See the GitHub repo for detailed testing instructions."
49+
]
50+
},
51+
"cleanup": {
52+
"text": [
53+
"Delete the stack: <code>sam delete</code>."
54+
]
55+
},
56+
"authors": [
57+
{
58+
"name": "Ravi Kiran Ganji",
59+
"bio": "I am a Senior Cloud Application Architect at AWS Professional Services, and Serverless Enthusiast.",
60+
"linkedin": "ravi-kiran-ganji"
61+
}
62+
]
63+
}

0 commit comments

Comments
 (0)