Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions cdk/my_service/api_construct.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ def __init__(self, scope: Construct, id_: str, appconfig_app_name: str) -> None:
super().__init__(scope, id_)
self.id_ = id_
self.api_db = ApiDbConstruct(self, f'{id_}db')
self.lambda_role = self._build_lambda_role(self.api_db.db)
self.lambda_role = self._build_lambda_role(self.api_db.db, self.api_db.idempotency_db)
self.common_layer = self._build_common_layer()
self.rest_api = self._build_api_gw()
api_resource: aws_apigateway.Resource = self.rest_api.root.add_resource('api').add_resource(constants.GW_RESOURCE)
self._add_post_lambda_integration(api_resource, self.lambda_role, self.api_db.db, appconfig_app_name)
self._add_post_lambda_integration(api_resource, self.lambda_role, self.api_db.db, appconfig_app_name, self.api_db.idempotency_db)

def _build_api_gw(self) -> aws_apigateway.RestApi:
rest_api: aws_apigateway.RestApi = aws_apigateway.RestApi(
Expand All @@ -35,7 +35,7 @@ def _build_api_gw(self) -> aws_apigateway.RestApi:
CfnOutput(self, id=constants.APIGATEWAY, value=rest_api.url).override_logical_id(constants.APIGATEWAY)
return rest_api

def _build_lambda_role(self, db: dynamodb.Table) -> iam.Role:
def _build_lambda_role(self, db: dynamodb.Table, idempotency_table: dynamodb.Table) -> iam.Role:
return iam.Role(
self,
constants.SERVICE_ROLE_ARN,
Expand All @@ -51,7 +51,19 @@ def _build_lambda_role(self, db: dynamodb.Table) -> iam.Role:
]),
'dynamodb_db':
iam.PolicyDocument(statements=[
iam.PolicyStatement(actions=['dynamodb:PutItem', 'dynamodb:GetItem'], resources=[db.table_arn], effect=iam.Effect.ALLOW)
iam.PolicyStatement(
actions=['dynamodb:PutItem', 'dynamodb:GetItem'],
resources=[db.table_arn],
effect=iam.Effect.ALLOW,
)
]),
'idempotency_table':
iam.PolicyDocument(statements=[
iam.PolicyStatement(
actions=['dynamodb:PutItem', 'dynamodb:GetItem', 'dynamodb:UpdateItem', 'dynamodb:DeleteItem'],
resources=[idempotency_table.table_arn],
effect=iam.Effect.ALLOW,
)
]),
},
managed_policies=[
Expand All @@ -68,7 +80,8 @@ def _build_common_layer(self) -> PythonLayerVersion:
removal_policy=RemovalPolicy.DESTROY,
)

def _add_post_lambda_integration(self, api_name: aws_apigateway.Resource, role: iam.Role, db: dynamodb.Table, appconfig_app_name: str):
def _add_post_lambda_integration(self, api_name: aws_apigateway.Resource, role: iam.Role, db: dynamodb.Table, appconfig_app_name: str,
idempotency_table: dynamodb.Table):
lambda_function = _lambda.Function(
self,
constants.CREATE_LAMBDA,
Expand All @@ -85,6 +98,7 @@ def _add_post_lambda_integration(self, api_name: aws_apigateway.Resource, role:
'REST_API': 'https://www.ranthebuilder.cloud/api', # for env vars example
'ROLE_ARN': 'arn:partition:service:region:account-id:resource-type:resource-id', # for env vars example
'TABLE_NAME': db.table_name,
'IDEMPOTENCY_TABLE_NAME': idempotency_table.table_name,
},
tracing=_lambda.Tracing.ACTIVE,
retry_attempts=0,
Expand Down
17 changes: 17 additions & 0 deletions cdk/my_service/api_db_construct.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ def __init__(self, scope: Construct, id_: str) -> None:
super().__init__(scope, id_)

self.db: dynamodb.Table = self._build_db(id_)
self.idempotency_db: dynamodb.Table = self._build_idempotency_table(id_)

def _build_idempotency_table(self, id_: str) -> dynamodb.Table:
table_id = f'{id_}{constants.IDEMPOTENCY_TABLE_NAME}'
table = dynamodb.Table(
self,
table_id,
table_name=table_id,
partition_key=dynamodb.Attribute(name='id', type=dynamodb.AttributeType.STRING),
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
removal_policy=RemovalPolicy.DESTROY,
time_to_live_attribute='expiration',
point_in_time_recovery=True,
)
CfnOutput(self, id=constants.IDEMPOTENCY_TABLE_NAME_OUTPUT,
value=table.table_name).override_logical_id(constants.IDEMPOTENCY_TABLE_NAME_OUTPUT)
return table

def _build_db(self, id_prefix: str) -> dynamodb.Table:
table_id = f'{id_prefix}{constants.TABLE_NAME}'
Expand Down
2 changes: 2 additions & 0 deletions cdk/my_service/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
SERVICE_ROLE = 'ServiceRole'
CREATE_LAMBDA = 'CreateOrder'
TABLE_NAME = 'orders'
IDEMPOTENCY_TABLE_NAME = 'IdempotencyTable'
TABLE_NAME_OUTPUT = 'DbOutput'
IDEMPOTENCY_TABLE_NAME_OUTPUT = 'IdempotencyDbOutput'
APIGATEWAY = 'Apigateway'
GW_RESOURCE = 'orders'
LAMBDA_LAYER_NAME = 'common'
Expand Down
9 changes: 5 additions & 4 deletions docs/cdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,17 @@ All ASW Lambda function configurations are saved as constants at the `cdk.my_ser
- **Lambda Function** - The Lambda handler function itself. Handler code is taken from the service `folder`.
- **Lambda Role** - The role of the Lambda function.
- **API GW with Lambda Integration** - API GW with a Lambda integration POST /api/orders that triggers the Lambda function.
- **AWS DynamoDB table** - stores request data. Created in its own construct: api_db_construct.py
- **AWS DynamoDB table** - stores request data. Created in the `api_db_construct.py` construct.
- **AWS DynamoDB table** - stores idempotency data. Created in the `api_db_construct.py` construct.
- Construct: **cdk.my_service.configuration.configuration_construct.py** which includes:
- AWS AppConfig configuration with an environment, application, configuration and deployment strategy. You can read more about it [here.](best_practices/dynamic_configuration.md)
- AWS AppConfig configuration with an environment, application, configuration and deployment strategy. You can read more about it [here](best_practices/dynamic_configuration.md).

### **Infrastructure CDK & Security Tests**

Under tests there is an `infrastructure` folder for CDK infrastructure tests.

The first test, 'test_cdk' uses CDK's testing framework which asserts that required resources exists so the application will not break anything upon deployment.
The first test, `test_cdk` uses CDK's testing framework which asserts that required resources exists so the application will not break anything upon deployment.

The security tests are based on 'cdk_nag'. It checks your cloudformation output for security best practices. It can be found in the 'service_stack.py' as part of the stack definition. It will fail the deployment when there is a security issue.
The security tests are based on `cdk_nag`. It checks your cloudformation output for security best practices. It can be found in the `service_stack.py` as part of the stack definition. It will fail the deployment when there is a security issue.

For more information click [here](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/check-aws-cdk-applications-or-cloudformation-templates-for-best-practices-by-using-cdk-nag-rule-packs.html){:target="_blank" rel="noopener"}.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from aws_lambda_powertools.utilities.parser.envelopes import ApiGatewayEnvelope
from aws_lambda_powertools.utilities.typing import LambdaContext

from service.handlers.schemas.input import Input
from .schema import Input


def my_handler(event: Dict[str, Any], context: LambdaContext):
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ This project aims to reduce cognitive load and answer these questions for you by
- AWS Lambda handler uses [AWS Lambda Powertools](https://awslabs.github.io/aws-lambda-powertools-python/){:target="_blank" rel="noopener"}.
- AWS Lambda handler 3 layer architecture: handler layer, logic layer and data access layer
- Features flags and configuration based on AWS AppConfig
- Idempotent API
- Unit, infrastructure, security, integration and E2E tests.

The GitHub template project can be found at [https://github.com/ran-isenberg/aws-lambda-handler-cookbook](https://github.com/ran-isenberg/aws-lambda-handler-cookbook){:target="_blank" rel="noopener"}.
Expand Down
Binary file modified docs/media/design.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ nav:
- best_practices/environment_variables.md
- best_practices/input_validation.md
- best_practices/dynamic_configuration.md
- Idempotency: https://awslabs.github.io/aws-lambda-powertools-python/2.16.2/utilities/idempotency/" target="_blank"
- CDK Best practices: https://www.ranthebuilder.cloud/post/aws-cdk-best-practices-from-the-trenches" target="_blank"
- Testing Best practices: https://www.ranthebuilder.cloud/post/guide-to-serverless-lambda-testing-best-practices-part-1" target="_blank"

Expand Down
Loading