Using Constructs
One of the amazing capabilities of CDK is the ability to encapsulate common IaC behaviors into a construct. This allows you to reuse tested constructs and reduce code bugs. In this section, we are going to encapsulate the API GW -> Lambda resources into a single construct and use it across the board.
We will encapsulate the API GW -> Lambda integration, which is a pivotal part of our architecture. The construct will create a single API Gateway and attach Lambda functions using an object add
method.
Implementation
Adding a construct
- Add a new file named
iac/api_gw_lambda_construct.py
, it will containt the construct we are building. - Insert the content into
iac/api_gw_lambda_construct.py
.
from constructs import Construct
from aws_cdk import (
aws_lambda_python_alpha as lambda_python,
aws_apigateway as apigw,
CfnOutput,
)
class LambdaApiGatewayConstruct(Construct): #(1)!
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
self.api = apigw.RestApi( #(2)!
self,
"MailingListAPI",
)
def add_lambda_integration( #(3)!
self, lambda_integration: lambda_python.PythonFunction, path: str, method: str
) -> None:
path_parts = path.split("/") #(4)!
resource = self.api.root
for part in path_parts:
resource = resource.get_resource(part) or resource.add_resource(part) #(5)!
resource.add_method(
method, apigw.LambdaIntegration(lambda_integration, allow_test_invoke=False)
)
CfnOutput(self, f"{method} {path}", value=f"{self.api.url}{path}") #(6)!
- Each Construct inherits from
Construct
. - We're creating a single API Gateway where we'll make modifications, such as adding Lambda integrations. It's up to you whether you want to export the API as a public property.
- This is where the magic occurs: you add a Lambda integration by invoking this method. We expect to receive a Lambda construct as a parameter.
- We need to split the path since each resource in API GW can only have one path element.
- We can't add the same resource twice, so check if it exists.
- We automatically output the new integration.
Using a construct
A construct is a regular Python class; just import it and use it directly in your code. Paste the following code to lambda_api_gateway_stack.py
from .api_gw_lambda_construct import LambdaApiGatewayConstruct #(1)!
from constructs import Construct
from aws_cdk import (
Stack,
aws_lambda_python_alpha as lambda_python,
aws_lambda,
aws_dynamodb as ddb,
)
class LambdaApiGatewayStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
table = ddb.Table(
self,
"SubscribersTable",
partition_key={"name": "group_name", "type": ddb.AttributeType.STRING},
sort_key={"name": "subscriber", "type": ddb.AttributeType.STRING},
)
layer = lambda_python.PythonLayerVersion(
self,
"MailingListLayer",
entry="mailinglist_app/src/group_subscription_layer",
compatible_runtimes=[aws_lambda.Runtime.PYTHON_3_11],
)
get_subscribers_function = lambda_python.PythonFunction( #(2)!
self,
"GetSubscribersFunction",
entry="mailinglist_app",
index="src/get_subscribers/app.py",
handler="lambda_handler",
runtime=aws_lambda.Runtime.PYTHON_3_11,
environment={"SUBSCRIBERS_TABLE": table.table_name},
layers=[layer],
)
api = LambdaApiGatewayConstruct(self, "LambdaAPIGWMailingList") #(3)!
api.add_lambda_integration( #(4)!
get_subscribers_function, "{group}/subscription", "GET"
)
table.grant_read_data(get_subscribers_function)
- Import the class.
- Continue creating the Lambda construct as we did previously.
- Instantiate the construct.
- Add the integration.
Deploy using cdk deploy
Exercises
- Add all the Lambdas from the previous AWS SAM section to the stack using this construct.
- Incorporate the DynamoDB table into this construct. Consider how you can encapsulate it.