HyperAI
Back to Headlines

Building a Serverless Backend with Amazon Aurora DSQL and AWS Lambda: Project Setup and Infrastructure Basics

6 days ago

Project Overview In this first part of a three-part series, we delve into building a serverless backend using Amazon Aurora DSQL, AWS Lambda, AWS CDK, and GitHub Actions. The article focuses on project setup, AWS CDK for infrastructure, establishing secure connectivity between Lambda and DSQL, and practical coding examples. The ultimate goal is to leverage the benefits of serverless computing and relational databases for scalable, secure, and maintainable applications. Project Setup The initial step involves organizing the project to ensure a clear separation between infrastructure, business logic, and CI/CD pipelines. The project structure is as follows: - infra/: Contains all CDK-related infrastructure code. - service/: Houses the Lambda function source code. - .github/: Stores GitHub Actions workflows for automation. This approach facilitates easy adaptation and scalability, especially as the project grows. AWS CDK for Infrastructure AWS Cloud Development Kit (CDK) is used to define cloud infrastructure as code. For this project, we utilize L1 Constructs for Aurora DSQL, as they are currently the only available option due to the recent release of DSQL. The StorageStack class creates a DSQL cluster using the CloudFormation CfnResource construct. Key properties include: - DeletionProtectionEnabled: Ensures the cluster cannot be accidentally deleted. - Tags: Adds metadata for project identification. ```typescript export class StorageStack extends cdk.Stack { dsqlCluster: cdk.CfnResource; dsqlClusterArn: string; dsqlClusterEndpoint: string; constructor(scope: constructs.Construct, id: string, props: cdk.StackProps) { super(scope, id, props); this.dsqlCluster = new cdk.CfnResource(this, 'DSQLCluster', { type: 'AWS::DSQL::Cluster', properties: { DeletionProtectionEnabled: true, Tags: [ { Key: 'Project', Value: 'aws-dsql-demo' } ] } }); this.dsqlClusterArn = this.dsqlCluster.getAtt('ResourceArn').toString(); this.dsqlClusterEndpoint = `${this.dsqlCluster.getAtt('Identifier').toString()}.dsql.${this.region}.on.aws`; } } ``` Lambda and DSQL Connectivity To enable secure communication between AWS Lambda and Aurora DSQL, we configure IAM roles and policies. The FunctionsStack class defines a Lambda function with the necessary permissions to interact with the DSQL cluster. ```typescript export interface FunctionsStackProps extends cdk.StackProps { label: { id: string; }; domainName: string; dsqlClusterEndpoint: string; dsqlClusterArn: string; } export class FunctionsStack extends cdk.Stack { generateGameLambda: lambda.Function; constructor(scope: constructs.Construct, id: string, props: FunctionsStackProps) { super(scope, `${id}-functions-stack`, props); const lambdaRole = new iam.Role(this, 'DSQLLambdaRole', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole') ] }); // Add DSQL permissions lambdaRole.addToPolicy(new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ 'dsql:DbConnect', 'dsql:ExecuteStatement', 'dsql:DbConnectAdmin' ], resources: [props.dsqlClusterArn] })); this.generateGameLambda = new nodeLambda.NodejsFunction(this, 'GenerateGameLambda', { entry: './service/generate-game/Handler.ts', runtime: lambda.Runtime.NODEJS_20_X, role: lambdaRole, environment: { DSQL_ENDPOINT: props.dsqlClusterEndpoint } }); } } ``` We also set the DSQL_ENDPOINT as an environment variable to facilitate dynamic configuration changes without manual intervention. Efficient Database Connection Management Efficiently managing database connections in Lambda is crucial due to the ephemeral nature and rapid scaling capabilities of serverless functions. To optimize performance, we create a DatabaseService class that caches the TypeORM DataSource connection and generates secure, temporary authentication tokens using @aws-sdk/dsql-signer. ```typescript import "reflect-metadata"; import { DataSource } from "typeorm"; import { DsqlSigner } from "@aws-sdk/dsql-signer"; import { Game } from "../models/Game"; export class DatabaseService { private static dataSource: DataSource; private static async getAuthToken(host: string): Promise<string> { const signer = new DsqlSigner({ hostname: host, region: process.env.AWS_REGION || 'eu-west-2' }); return await signer.getDbConnectAdminAuthToken(); } static async initialize(): Promise<DataSource> { if (!DatabaseService.dataSource) { const host = process.env.DSQL_ENDPOINT || ''; DatabaseService.dataSource = new DataSource({ type: "postgres", host: host, port: 5432, username: 'admin', password: await DatabaseService.getAuthToken(host), database: "postgres", ssl: { rejectUnauthorized: true }, synchronize: true, logging: true, entities: [Game] }); await DatabaseService.dataSource.initialize(); } return DatabaseService.dataSource; } static async saveGame(game: Partial<Game>): Promise<Game> { const dataSource = await DatabaseService.initialize(); const gameRepository = dataSource.getRepository(Game); const newGame = gameRepository.create(game); return await gameRepository.save(newGame); } } ``` Lambda Handler The Lambda handler function handles incoming API Gateway events, generates a random game, and saves it to the DSQL database. Proper error handling is implemented to provide meaningful responses in case of failures. ```typescript export async function handler(event: APIGatewayProxyEvent, context: Context): Promise { console.log(event, context); await DatabaseService.initialize(); try { const gameData = generateRandomGame(); const savedGame = await DatabaseService.saveGame(gameData); return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'Game generated and saved successfully', game: savedGame }) }; } catch (error) { console.error('Error generating game:', error); return { statusCode: 500, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'Failed to generate and save game', error: error instanceof Error ? error.message : String(error) }) }; } } ``` Open Source Repository The article references an open-source repository, providing complete project transparency. Readers can clone the repository to explore the project structure and code, making it easier to adapt the setup to their own projects. Industry Insights and Company Profiles Industry insiders laud the combination of AWS Lambda and Aurora DSQL for its potential to simplify database management in serverless environments. The automatic scaling and pay-per-use model of DSQL, coupled with the flexibility and cost-effectiveness of Lambda, offer significant advantages for developing agile and scalable applications. This approach is particularly beneficial for startups and enterprises looking to reduce operational overhead while maintaining robust data management capabilities. Amazon Web Services (AWS) has consistently pushed the boundaries of cloud computing with innovative services like Aurora DSQL. The company's commitment to serverless technologies underscores its vision for a more efficient and accessible cloud infrastructure, aligning with the evolving needs of modern applications. This project serves as a practical guide for developers and teams looking to integrate relational databases into their serverless architectures, demonstrating the power and simplicity of AWS services.

Related Links