Skip to content

Examples

Real-world usage examples and patterns for LayeredCraft CDK Constructs.

Complete Application Examples

Serverless API with Database

A complete serverless API with Lambda, DynamoDB, and API Gateway.

using Amazon.CDK;
using Amazon.CDK.AWS.IAM;
using LayeredCraft.Cdk.Constructs;
using LayeredCraft.Cdk.Constructs.Models;

public class ServerlessApiStack : Stack
{
    public ServerlessApiStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
    {
        // Create DynamoDB table for user data
        var userTable = new DynamoDbTableConstruct(this, "UserTable", new DynamoDbTableConstructProps
        {
            TableName = "users",
            PartitionKey = new AttributeDefinition { AttributeName = "userId", AttributeType = AttributeType.STRING },
            GlobalSecondaryIndexes = [
                new GlobalSecondaryIndex
                {
                    IndexName = "email-index",
                    PartitionKey = new AttributeDefinition { AttributeName = "email", AttributeType = AttributeType.STRING },
                    ProjectionType = ProjectionType.ALL
                }
            ]
        });

        // Create Lambda function for API
        var apiLambda = new LambdaFunctionConstruct(this, "ApiLambda", new LambdaFunctionConstructProps
        {
            FunctionName = "user-api",
            FunctionSuffix = "prod",
            AssetPath = "./api-lambda.zip",
            RoleName = "user-api-role",
            PolicyName = "user-api-policy",
            MemorySize = 1024,
            TimeoutInSeconds = 30,
            PolicyStatements = [
                new PolicyStatement(new PolicyStatementProps
                {
                    Actions = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:Query", "dynamodb:UpdateItem"],
                    Resources = [userTable.TableArn, $"{userTable.TableArn}/index/*"],
                    Effect = Effect.ALLOW
                })
            ],
            EnvironmentVariables = new Dictionary<string, string>
            {
                { "TABLE_NAME", userTable.TableName },
                { "EMAIL_INDEX", "email-index" }
            },
            GenerateUrl = true // Enable Function URL for direct HTTP access
        });
    }
}

Static Website with API Backend

A static website with API backend proxying.

public class WebsiteStack : Stack
{
    public WebsiteStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
    {
        // Create API Lambda
        var apiLambda = new LambdaFunctionConstruct(this, "ApiLambda", new LambdaFunctionConstructProps
        {
            FunctionName = "website-api",
            FunctionSuffix = "prod",
            AssetPath = "./api-lambda.zip",
            RoleName = "website-api-role",
            PolicyName = "website-api-policy",
            GenerateUrl = true
        });

        // Create static website with API proxy
        var website = new StaticSiteConstruct(this, "Website", new StaticSiteConstructProps
        {
            SiteBucketName = "my-website-bucket",
            DomainName = "mywebsite.com",
            SiteSubDomain = "www",
            ApiDomain = apiLambda.LiveAliasFunctionUrlDomain!, // Proxy /api/* to Lambda
            AssetPath = "./website-build"
        });
    }
}

Event-Driven Architecture

Event sourcing with DynamoDB streams and Lambda processors.

public class EventDrivenStack : Stack
{
    public EventDrivenStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
    {
        // Event store table
        var eventTable = new DynamoDbTableConstruct(this, "EventTable", new DynamoDbTableConstructProps
        {
            TableName = "events",
            PartitionKey = new AttributeDefinition { AttributeName = "aggregateId", AttributeType = AttributeType.STRING },
            SortKey = new AttributeDefinition { AttributeName = "timestamp", AttributeType = AttributeType.NUMBER },
            StreamSpecification = StreamViewType.NEW_AND_OLD_IMAGES
        });

        // Read model table
        var readModelTable = new DynamoDbTableConstruct(this, "ReadModelTable", new DynamoDbTableConstructProps
        {
            TableName = "user-projections",
            PartitionKey = new AttributeDefinition { AttributeName = "userId", AttributeType = AttributeType.STRING }
        });

        // Event processor Lambda
        var eventProcessor = new LambdaFunctionConstruct(this, "EventProcessor", new LambdaFunctionConstructProps
        {
            FunctionName = "event-processor",
            FunctionSuffix = "prod",
            AssetPath = "./event-processor.zip",
            RoleName = "event-processor-role",
            PolicyName = "event-processor-policy",
            PolicyStatements = [
                new PolicyStatement(new PolicyStatementProps
                {
                    Actions = ["dynamodb:PutItem", "dynamodb:UpdateItem"],
                    Resources = [readModelTable.TableArn],
                    Effect = Effect.ALLOW
                })
            ],
            EnvironmentVariables = new Dictionary<string, string>
            {
                { "READ_MODEL_TABLE", readModelTable.TableName }
            }
        });

        // Connect stream to processor
        eventTable.AttachStreamLambda(eventProcessor.LambdaFunction);
    }
}

Common Patterns

Lambda with Multiple AWS Services

var lambda = new LambdaFunctionConstruct(this, "MultiServiceLambda", new LambdaFunctionConstructProps
{
    FunctionName = "multi-service",
    FunctionSuffix = "prod",
    AssetPath = "./lambda.zip",
    RoleName = "multi-service-role",
    PolicyName = "multi-service-policy",
    PolicyStatements = [
        // DynamoDB access
        new PolicyStatement(new PolicyStatementProps
        {
            Actions = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:Query"],
            Resources = ["arn:aws:dynamodb:us-east-1:123456789012:table/MyTable"],
            Effect = Effect.ALLOW
        }),
        // S3 access
        new PolicyStatement(new PolicyStatementProps
        {
            Actions = ["s3:GetObject", "s3:PutObject"],
            Resources = ["arn:aws:s3:::my-bucket/*"],
            Effect = Effect.ALLOW
        }),
        // SES access
        new PolicyStatement(new PolicyStatementProps
        {
            Actions = ["ses:SendEmail"],
            Resources = ["*"],
            Effect = Effect.ALLOW
        })
    ],
    EnvironmentVariables = new Dictionary<string, string>
    {
        { "TABLE_NAME", "MyTable" },
        { "BUCKET_NAME", "my-bucket" },
        { "FROM_EMAIL", "noreply@mycompany.com" }
    }
});

Lambda with API Gateway Integration

// Lambda function
var apiLambda = new LambdaFunctionConstruct(this, "ApiLambda", new LambdaFunctionConstructProps
{
    FunctionName = "api-handler",
    FunctionSuffix = "prod",
    AssetPath = "./api-lambda.zip",
    RoleName = "api-handler-role",
    PolicyName = "api-handler-policy",
    Permissions = [
        new LambdaPermission
        {
            Principal = "apigateway.amazonaws.com",
            Action = "lambda:InvokeFunction",
            SourceArn = "arn:aws:execute-api:us-east-1:123456789012:*/*/GET/users"
        },
        new LambdaPermission
        {
            Principal = "apigateway.amazonaws.com",
            Action = "lambda:InvokeFunction",
            SourceArn = "arn:aws:execute-api:us-east-1:123456789012:*/*/POST/users"
        }
    ]
});

// API Gateway would be created separately using standard CDK constructs

Static Site with Multiple Domains

var website = new StaticSiteConstruct(this, "Website", new StaticSiteConstructProps
{
    SiteBucketName = "my-website-bucket",
    DomainName = "mycompany.com",
    SiteSubDomain = "www",
    AlternateDomains = [
        "mycompany.org",
        "www.mycompany.org",
        "mycompany.net",
        "www.mycompany.net"
    ],
    AssetPath = "./website-build"
});

DynamoDB with Complex Indexing

var table = new DynamoDbTableConstruct(this, "ComplexTable", new DynamoDbTableConstructProps
{
    TableName = "user-activities",
    PartitionKey = new AttributeDefinition { AttributeName = "userId", AttributeType = AttributeType.STRING },
    SortKey = new AttributeDefinition { AttributeName = "timestamp", AttributeType = AttributeType.NUMBER },
    GlobalSecondaryIndexes = [
        // Query by activity type
        new GlobalSecondaryIndex
        {
            IndexName = "activity-type-index",
            PartitionKey = new AttributeDefinition { AttributeName = "activityType", AttributeType = AttributeType.STRING },
            SortKey = new AttributeDefinition { AttributeName = "timestamp", AttributeType = AttributeType.NUMBER },
            ProjectionType = ProjectionType.ALL
        },
        // Query by status
        new GlobalSecondaryIndex
        {
            IndexName = "status-index",
            PartitionKey = new AttributeDefinition { AttributeName = "status", AttributeType = AttributeType.STRING },
            SortKey = new AttributeDefinition { AttributeName = "timestamp", AttributeType = AttributeType.NUMBER },
            ProjectionType = ProjectionType.KEYS_ONLY
        }
    ],
    StreamSpecification = StreamViewType.NEW_AND_OLD_IMAGES,
    TimeToLiveAttribute = "expiresAt"
});

Testing Examples

Complete Test Suite

[Collection("CDK Tests")]
public class ServerlessApiStackTests
{
    [Fact]
    public void Should_Create_Complete_Api_Stack()
    {
        // Arrange
        var app = new App();
        var stack = new ServerlessApiStack(app, "test-stack", new StackProps
        {
            Env = new Amazon.CDK.Environment { Account = "123456789012", Region = "us-east-1" }
        });

        // Act
        var template = Template.FromStack(stack);

        // Assert
        template.ShouldHaveDynamoTable("users");
        template.ShouldHaveGlobalSecondaryIndex("email-index", "email", AttributeType.STRING);
        template.ShouldHaveLambdaFunction("user-api-prod");
        template.ShouldHaveFunctionUrl();
        template.ShouldHaveEnvironmentVariables(new Dictionary<string, string>
        {
            { "TABLE_NAME", "users" },
            { "EMAIL_INDEX", "email-index" }
        });
    }
}

Parameterized Testing

[Collection("CDK Tests")]
public class EnvironmentSpecificTests
{
    [Theory]
    [InlineData("dev", 512, 10)]
    [InlineData("staging", 1024, 15)]
    [InlineData("prod", 2048, 30)]
    public void Should_Configure_Lambda_For_Environment(string env, int memory, int timeout)
    {
        // Arrange
        var props = CdkTestHelper.CreatePropsBuilder(AssetPathExtensions.GetTestLambdaZipPath())
            .WithFunctionSuffix(env)
            .WithMemorySize(memory)
            .WithTimeoutInSeconds(timeout)
            .Build();

        var (app, stack) = CdkTestHelper.CreateTestStack("test-stack");

        // Act
        var construct = new LambdaFunctionConstruct(stack, "test-construct", props);
        var template = Template.FromStack(stack);

        // Assert
        template.ShouldHaveLambdaFunction($"test-function-{env}");
        template.ShouldHaveMemorySize(memory);
        template.ShouldHaveTimeout(timeout);
    }
}

Best Practices Examples

Environment-Specific Configuration

public class EnvironmentConfig
{
    public string Environment { get; set; }
    public int LambdaMemory { get; set; }
    public int LambdaTimeout { get; set; }
    public bool EnableTracing { get; set; }
    public string DomainName { get; set; }
}

public class MyStack : Stack
{
    public MyStack(Construct scope, string id, EnvironmentConfig config, IStackProps props = null) 
        : base(scope, id, props)
    {
        var lambda = new LambdaFunctionConstruct(this, "Lambda", new LambdaFunctionConstructProps
        {
            FunctionName = "my-function",
            FunctionSuffix = config.Environment,
            AssetPath = "./lambda.zip",
            RoleName = $"my-function-{config.Environment}-role",
            PolicyName = $"my-function-{config.Environment}-policy",
            MemorySize = config.LambdaMemory,
            TimeoutInSeconds = config.LambdaTimeout,
            IncludeOtelLayer = config.EnableTracing
        });

        if (!string.IsNullOrEmpty(config.DomainName))
        {
            var site = new StaticSiteConstruct(this, "Site", new StaticSiteConstructProps
            {
                SiteBucketName = $"my-site-{config.Environment}",
                DomainName = config.DomainName,
                AssetPath = "./site-build"
            });
        }
    }
}

Resource Naming Conventions

public class ResourceNaming
{
    private readonly string _environment;
    private readonly string _application;

    public ResourceNaming(string application, string environment)
    {
        _application = application;
        _environment = environment;
    }

    public string Lambda(string component) => $"{_application}-{component}";
    public string LambdaRole(string component) => $"{_application}-{component}-{_environment}-role";
    public string Table(string entity) => $"{_application}-{entity}-{_environment}";
    public string Bucket(string purpose) => $"{_application}-{purpose}-{_environment}";
}

public class MyStack : Stack
{
    public MyStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
    {
        var naming = new ResourceNaming("myapp", "prod");

        var lambda = new LambdaFunctionConstruct(this, "Lambda", new LambdaFunctionConstructProps
        {
            FunctionName = naming.Lambda("api"),
            FunctionSuffix = "prod",
            AssetPath = "./lambda.zip",
            RoleName = naming.LambdaRole("api"),
            PolicyName = naming.LambdaRole("api").Replace("-role", "-policy")
        });
    }
}

Deployment Examples

Multi-Stack Application

public class Program
{
    public static void Main(string[] args)
    {
        var app = new App();
        var env = new Amazon.CDK.Environment
        {
            Account = System.Environment.GetEnvironmentVariable("CDK_DEFAULT_ACCOUNT"),
            Region = System.Environment.GetEnvironmentVariable("CDK_DEFAULT_REGION")
        };

        // Infrastructure stack
        var infrastructure = new InfrastructureStack(app, "MyApp-Infrastructure", new StackProps { Env = env });

        // API stack (depends on infrastructure)
        var api = new ApiStack(app, "MyApp-Api", new ApiStackProps 
        { 
            Env = env,
            UserTable = infrastructure.UserTable
        });

        // Website stack (depends on API)
        var website = new WebsiteStack(app, "MyApp-Website", new WebsiteStackProps 
        { 
            Env = env,
            ApiUrl = api.ApiUrl
        });

        app.Synth();
    }
}

For more examples, see the test files in the repository which demonstrate comprehensive usage patterns.