This document provides detailed information about using the Mayfly Elixir runtime for AWS Lambda.
Mayfly implements the AWS Lambda Runtime API to enable Elixir code to run in AWS Lambda. The runtime:
- Starts an Elixir application
- Polls the Lambda Runtime API for invocations
- Executes the specified handler function
- Returns the result to the Lambda Runtime API
The main application module that starts the supervision tree and initializes the Lambda event loop.
A GenServer that continuously polls for new invocations and processes them. It:
- Fetches invocations from the Lambda Runtime API
- Resolves and executes the handler function
- Sends responses back to the Lambda Runtime API
Handles communication with the AWS Lambda Runtime API, providing functions for:
- Fetching invocations
- Sending successful responses
- Reporting errors
Manages handler resolution and execution:
- Resolves the handler module and function from environment variables
- Executes the handler function with proper error handling
- Provides a default handler when none is specified
Provides standardized error handling and formatting:
- Converts various error types to a standardized Lambda error format
- Formats stacktraces for error reporting
Your handler function should:
- Accept a single map parameter (the decoded JSON payload)
- Return either
{:ok, result}or{:error, reason}
Example:
def my_handler(payload) do
case process_data(payload) do
{:ok, result} -> {:ok, result}
{:error, reason} -> {:error, reason}
end
end- Build:
mix lambda.build --zipcreates a deployment package - Deploy: Upload the ZIP file to AWS Lambda
- Configure: Set the handler environment variable
-
Add Mayfly to your dependencies:
def deps do [ {:mayfly, git: "https://github.com/bmalum/mayfly", branch: "main"} ] end
-
Create your handler function:
defmodule MyApp.Handler do def process(payload) do # Your business logic here {:ok, %{result: "Success", data: transformed_data}} end end
-
Build the Lambda package:
mix lambda.build --zip
-
Create a Lambda function in AWS:
- Runtime: Custom runtime
- Handler:
Elixir.MyApp.Handler.process - Upload the generated
lambda.zipfile
You can define custom error types for better error reporting:
defmodule MyApp.ValidationError do
defexception message: "Validation failed"
end
def handler(payload) do
case validate(payload) do
:ok -> {:ok, process(payload)}
{:error, reason} -> raise MyApp.ValidationError, message: reason
end
endTo handle binary data in responses:
def generate_image(payload) do
# Generate image data
image_data = create_image(payload)
{:ok, %{
isBase64Encoded: true,
body: Base.encode64(image_data),
headers: %{"Content-Type" => "image/png"}
}}
endWhen integrating with API Gateway, structure your response like this:
def api_handler(payload) do
{:ok, %{
statusCode: 200,
headers: %{
"Content-Type" => "application/json"
},
body: Jason.encode!(%{message: "Hello from Elixir!"})
}}
end_HANDLER: The Elixir function to call, in the formatElixir.Module.functionAWS_LAMBDA_RUNTIME_API: Set automatically by AWS LambdaLOGLEVEL: Set todebugfor more verbose logging (optional)
- Handler Not Found: Ensure the
_HANDLERenvironment variable is correctly set toElixir.Module.function - Timeout Errors: Check if your function exceeds the Lambda timeout limit
- Memory Issues: Increase the Lambda memory allocation if needed
- Cold Start Performance: Consider increasing the memory allocation to improve cold start times
To enable debug logging, set the LOGLEVEL environment variable to debug.
- "Handler not found": The specified handler module or function doesn't exist
- "Invalid response format": The handler function returned an invalid response format
- "Initialization error": An error occurred during the Lambda initialization phase
- Keep Functions Small: Focus on a single responsibility
- Handle Errors Properly: Always return
{:ok, result}or{:error, reason} - Validate Input: Always validate and sanitize input data
- Use Proper Logging: Use Logger for debugging and monitoring
- Optimize Cold Starts: Minimize dependencies and code size
- Use Environment Variables: For configuration and secrets
- Test Locally: Test your functions locally before deployment
- Memory Allocation: Higher memory allocation also means more CPU power
- Cold Starts: First invocation will be slower due to the JVM startup time
- Connection Pooling: Reuse connections for external services
- Stateless Design: Don't rely on state between invocations
- Input Validation: Always validate and sanitize input data
- Secrets Management: Use environment variables or AWS Secrets Manager
- Least Privilege: Use IAM roles with minimal permissions
- Dependency Scanning: Regularly scan dependencies for vulnerabilities