Integration & Automation

Best practices for accelerating development with serverless blueprints

In this article, we present best practices for building great serverless blueprints that promote standardization, scalability, and repeatable deployment patterns throughout your organization. These best practices are founded on the principle that a good serverless blueprint is not just about simplification; it’s an automation tool that seamlessly integrates with testing and continuous integration and continuous delivery (CI/CD) pipelines.

A well-designed serverless blueprint adheres to AWS Lambda handler best practices, ensures observability for real-time insights, and maintains coherent project and Lambda handler structures. In addition, using infrastructure as code (IaC) tools such as AWS Cloud Development Kit (AWS CDK), results in a secure and repeatable development and deployment process.

About this blog post
Time to read ~10 min
Time to complete ~30 min
Learning level Advanced (300)
AWS services Amazon API Gateway
Amazon DynamoDB
AWS Cloud Development Kit (AWS CDK)
AWS Lambda

Overview

Serverless blueprints, commonly called templates, help accelerate development while maintaining best practices and quality across the organization. By providing preconfigured templates for quick setup and initial configuration, blueprints significantly speed up adoption of serverless technology and give your team the confidence they need to build robust applications.

In the sections that follow, we walk you through a list of best practices and components that define a great blueprint while exploring a fully working, serverless blueprint.

We use examples and sample code from AWS Lambda Handler Cookbook, an open-source blueprint template for deploying an orders service with AWS CDK. AWS CDK helps you create your infrastructure as code (IaC) using Python or TypeScript and translating it in a format of AWS CloudFormation template. The orders service is available as a skeleton Python service template that uses Amazon API Gateway and AWS Lambda function to create a customer order and then save it to an Amazon DynamoDB table, as shown in the following diagram.

Diagram of serverless blueprint

The template shows usage examples of the following options, several of which are covered in the list of best practices that follow:

  • Synchronous services API Gateway and Lambda.
  • Database integration with DynamoDB.
  • Backend components built using AWS CDK and AWS CloudFormation.

Prerequisites

Prerequisites include:

  • A working knowledge of Amazon DynamoDB.
  • Prerequisite requirements as listed in AWS Lambda Handler Cookbook. See the Getting Started Guide for a complete list.

Best practices

Learn about best practices as you refer to the AWS Lambda Handler Cookbook for real-world examples.

Project and folder structure

Organize your blueprint repository’s project structure with a serverless mindset. From an overall project perspective, store your infrastructure as code (IaC) files and the Lambda function handlers together in the same project so that your developers can work independently and take more ownership over their code. From a folder structure perspective, store the business domain code (the heart of your project) and the Lambda function handler files in one folder, with the IaC files organized in a separate folder.

Unified observability

As you design your serverless blueprint, take steps to implement unified observability, a methodology that uses monitoring tools to combine data from serverless resources into a single view. To learn best practices for implementing unified observability, see this blog post.

With unified observability, developers can more easily extend and maintain monitoring capabilities when they add new serverless resources. Also, when the same observability tools are used across the organization, developer teams and site reliability engineers (SREs) use a shared language to debug production issues across transactions, resulting in a more streamlined and efficient flow.

The AWS Lambda Handler Cookbook provides an example of a robust unified observability configuration that uses Amazon CloudWatch dashboards and alarms to monitor your resources. The IaC files create a CloudWatch dashboard with logs and metrics that monitor the service’s resources. These files also create CloudWatch alarms that are triggered when a data point, such as a metric’s threshold or an error log, is detected. These alarms send a message to an Amazon Simple Notification Service (SNS) that notifies SREs or developers.

Lambda function patterns

To ensure that you follow best practices for Lambda functions, use hexagonal architecture and observability patterns.

Hexagonal architecture

Hexagonal architecture is a software design pattern that promotes high modularity and a clear separation of external dependencies such as databases or external services in application development. The critical concept of hexagonal architecture is to decouple the core business logic or domain model from these external dependencies using ports and adapters.

Using hexagonal architecture, the codebase is easier to understand, maintain, and test. Changes to external dependencies or user interfaces don’t affect the core business logic, and you can deploy the service in different environments or platforms by simply swapping out the adapters.

The Lambda function used in the AWS Lambda Handler Cookbook follows a hexagonal architecture pattern by structuring the following three architectural layers into individual folders:

For more information, see Learn How to Write AWS Lambda Functions with Three Architectural Layers.

Observability

Implement AWS Lambda function observability to measure the current state of your system using logs, tracing, and metrics. While monitoring helps identify issues in your system, observability can help determine their causes.

In the AWS Lambda Handler Cookbook, the Lambda function handler is designed to create a shared and unified observability language by doing the following:

  • Using a logger library to write JSON logs and a correlation ID to CloudWatch to identify a customer request across services.
  • Publishing custom CloudWatch metrics for key performance indicators (KPI) capabilities.
  • Using AWS X-Ray to enable tracing to help identify performance bottlenecks.

In addition, the Lambda function handler uses Powertools for the AWS Lambda (Python), a developer toolkit and library, to implement the following utilities:

  • Logger to write JSON logs to CloudWatch.
  • Tracer to generate AWS X-ray traces.
  • Metrics to write CloudWatch custom metrics.

Powertools for AWS Lambda offers a wide range of best-practice utilities, including an event handler, input validation, error handling. The following code from the cookbook shows you how to embed them into your Lambda function. This create order handler code responds to HTTP POST REST API calls to /API/orders. It includes examples of log writing, custom CloudWatch metric generation, AWS X-Ray trace generation, input validation, and error handling.

from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.logging import Logger, correlation_paths
from aws_lambda_powertools.metrics import Metrics, MetricUnit
from aws_lambda_powertools.tracing import Tracer

from service.logic.create_order import create_order
from service.models.input import CreateOrderRequest
from service.models.output import CreateOrderOutput

app = APIGatewayRestResolver(enable_validation=True)
logger: Logger = Logger()
tracer: Tracer = Tracer()
metrics = Metrics()

@app.post('/api/orders/')
def handle_create_order(create_input: CreateOrderRequest) -> CreateOrderOutput:
    # log the incoming request, log the pydantic model
    logger.info('got create order request', order=create_input.model_dump())

    # write CloudWatch custom metric
    metrics.add_metric(name='ValidCreateOrderEvents', unit=MetricUnit.Count, value=1)

    # call the domain layer
    response = create_order(
        order_request=create_input,
        table_name='ranisenberg-deps-Orders-dev-Cruddborders',
        context=app.lambda_context,
    )

    # return the response
    logger.info('finished handling create order request')
    return response

@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
@metrics.log_metrics
@tracer.capture_lambda_handler(capture_response=False)
def lambda_handler(event, context):
    # route the request to the appropriate handler
    return app.resolve(event, context)

CI/CD pipeline

The CI/CD pipeline is a key element of the serverless template because it gives developers more time to focus on their business domain code and less time to worry about post-deployment errors and security issues. With more time to spend on developing high-quality code, developers experience a much more seamless and smooth process when deploying services to production.

In the AWS Lambda Handler Cookbook, the CI/CD pipeline is designed to ensure security standardization, secret scanning, quality assurance, and testing across serverless applications. Tools such as CDK-nag and CFN-lint scan the AWS CloudFormation template before deployment to identify misconfiguration and security risks. The two primary functions include building the Lambda functions that contain the business domain code and deploying the project’s resources to AWS using the IaC files.

Testing

Always test your serverless applications to validate that your developers have adequately defined and configured the serverless infrastructure and that the business domain code functions correctly. Regardless of which testing approach you choose, your serverless blueprint must include well-written tests and provide coverage for the most important aspects of your organization. If needed, look for predefined blueprint tests so that your developers understand expectations when developing new features.

The AWS Lambda Handler Cookbook defines the CI/CD pipeline for orchestrating the deployment and performing a test run. It includes three primary test types—unit, integration, and end-to-end tests. Your organization might use different names for these tests, but the concepts are similar. Depending on your architecture and the amount of flexibility required, various methods are available to help you implement these tests, for example mocks, ephemeral environments, or the debugging feature included with AWS Serverless Application Model (AWS SAM).

For more information about this testing approach and each test definition used in the AWS Lambda Handler Cookbook, see this AWS re:Invent session recording from 2023.

Conclusion

In this article, you learned about the components of a great serverless blueprint and saw them in action with a real-world template. We hope our approach will serve as a guide as you scale your applications while maintaining best practices, a unified structure, unified observability, and high quality.

For a deeper dive into the cookbook example that we referred to throughout the article, visit the AWS Lambda Handler Cookbook Getting Started guide.

If you have comments about this content, please leave them in the comments area below.

About the authors

Shani Adadi KazazShani Adadi Kazaz

Shani has been working at AWS since 2018 in multiple job roles, including software developer, then technical account manager (TAM), and now AppMod GTM specialist in Israel. Outside of work, Shani loves to travel, cook, and listen to good music.

Ran IsenbergRan Isenberg

Ran Isenberg is an AWS Serverless Hero, a Principal Software Architect at CyberArk, a blogger, and a public speaker. He maintains the RanTheBuilder.cloud blog where he shares knowledge and experience in the Serverless world.