Skip to content

bmalum/mayfly

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🪰 Mayfly

Mayfly Logo

A lightweight AWS Lambda Custom Runtime for Elixir

Version Elixir License

Why Mayfly?

Mayfly lets you leverage the full power of Elixir in AWS Lambda without compromise. Run your Elixir code in a serverless environment with:

  • Zero Boilerplate - Focus on your business logic, not Lambda implementation details
  • Native Elixir Experience - Use the same coding patterns and libraries you love
  • Optimized Performance - Designed specifically for Elixir's strengths in AWS Lambda
  • Proper Error Handling - Get meaningful stack traces and error reports

Unlike generic custom runtimes, Mayfly is purpose-built for Elixir, bringing the language's reliability and expressiveness to serverless functions.

Quick Start

# 1. Add Mayfly to your dependencies
# In mix.exs
def deps do
  [
    {:mayfly, github: "bmalum/mayfly"}
  ]
end

# 2. Create your handler function
defmodule MyFunction do
  def handle(event) do
    {:ok, %{message: "Hello from Elixir!", event: event}}
  end
end

# 3. Build and deploy
# Terminal
$ mix lambda.build --zip
# Upload lambda.zip to AWS Lambda and set handler to "Elixir.MyFunction.handle"

Table of Contents

Features

  • Full Elixir Support: Run any Elixir code in AWS Lambda, including GenServers and OTP applications
  • Simple API: Clean, idiomatic interface for Lambda functions with minimal boilerplate
  • Robust Error Handling: Get meaningful error reports with proper Elixir stacktraces
  • Build Tooling: Mix tasks for packaging and deployment with Docker and local build support
  • Flexible Integration: Works seamlessly with API Gateway, S3, EventBridge and other AWS services

Requirements

  • Elixir ~> 1.15
  • AWS Account with Lambda access
  • Mix

Installation

Add Mayfly to your dependencies in mix.exs:

def deps do
  [
    {:mayfly, github: "bmalum/mayfly"}
  ]
end

Run mix deps.get to fetch the dependency.

Usage

Creating a Lambda Function

  1. Define your handler function

    Create a module with a function that accepts a map and returns {:ok, result} or {:error, reason}:

    defmodule MyLambda do
      def handle(payload) do
        # Process the payload
        {:ok, %{message: "Hello from Elixir!", received: payload}}
      end
    end
  2. Build your Lambda package

    mix lambda.build --zip
  3. Deploy to AWS Lambda

    • Create a new Lambda function in the AWS Console
    • Select "Custom runtime" as the runtime
    • Upload the generated lambda.zip file
    • Set the handler environment variable to Elixir.MyLambda.handle

Advanced Examples

API Gateway Integration

When integrating with API Gateway, structure your response like this:

defmodule Api.Handler do
  def process(event) do
    {:ok, %{
      statusCode: 200,
      headers: %{
        "Content-Type" => "application/json"
      },
      body: Jason.encode!(%{
        message: "Hello from Elixir!",
        path: event["path"],
        method: event["httpMethod"]
      })
    }}
  end
end

Error Handling

defmodule Example.WithError do
  def handle(%{"should_fail" => true}) do
    {:error, "Requested failure"}
  end
  
  def handle(%{"raise_error" => true}) do
    raise "Demonstrating error handling"
  end
  
  def handle(payload) do
    {:ok, %{status: "success", payload: payload}}
  end
end

Handling Binary Data

defmodule ImageGenerator do
  def generate(payload) do
    # Generate image data
    image_data = create_image(payload)
    
    {:ok, %{
      isBase64Encoded: true,
      body: Base.encode64(image_data),
      headers: %{"Content-Type" => "image/png"}
    }}
  end
  
  defp create_image(payload) do
    # Implementation details...
  end
end

Documentation

Online Documentation

Full documentation with guides and API reference is available at elixir-aws-lambda.dev/docs

Quick links:

Generate Documentation Locally

Generate and view the documentation on your machine:

mix docs
open doc/index.html

Configuration

Environment Variables

  • _HANDLER: Required - The Elixir function to call, in the format Elixir.Module.function
  • AWS_LAMBDA_RUNTIME_API: Automatically set by AWS Lambda
  • LOGLEVEL: Optional - Set to debug for more verbose logging

Build Options

The lambda.build mix task supports the following options:

  • --zip: Create a ZIP file for deployment
  • --outdir: Specify the output directory (default: current directory)
  • --docker: Build using Docker (useful for cross-platform compatibility)

Example:

mix lambda.build --zip --docker

Custom Docker Build Environment

You can provide your own lambda.Dockerfile in your project root to customize the build environment. This is useful when:

  • Your dependencies require specific system libraries
  • You need a different Erlang/Elixir version
  • You want to add native dependencies for NIFs

Create a lambda.Dockerfile in your project root:

FROM amazonlinux:2023

# Add your custom system dependencies
RUN yum install -y imagemagick-devel libxml2-devel

# Install Erlang/Elixir (customize versions as needed)
RUN yum install -y wget tar gcc make && \
    wget https://github.com/erlang/otp/releases/download/OTP-27.2/otp_src_27.2.tar.gz && \
    tar -zxf otp_src_27.2.tar.gz && \
    cd otp_src_27.2 && \
    ./configure --without-javac && \
    make -j$(nproc) && make install && \
    cd / && rm -rf otp_src_27.2*

ENV MIX_ENV=lambda
WORKDIR /mnt/code
RUN mix local.rebar --force && mix local.hex --force

If no lambda.Dockerfile is found in your project, Mayfly will use the default one from the library.

Error Handling

Mayfly provides standardized error handling for Lambda functions:

  1. Return {:error, reason}: For expected errors

    def handle(payload) do
      case validate(payload) do
        :ok -> {:ok, process(payload)}
        {:error, reason} -> {:error, reason}
      end
    end
  2. Raise an exception: For unexpected errors

    def handle(payload) do
      # This will be caught and formatted properly
      result = payload["a"] + payload["b"]
      {:ok, %{sum: result}}
    end

Errors are formatted according to the AWS Lambda error response format with proper stacktraces.

Deployment

Detailed Deployment Steps

  1. Build the Lambda package:

    mix lambda.build --zip
  2. Create a new Lambda function:

    • Open the AWS Lambda Console
    • Click "Create function"
    • Choose "Author from scratch"
    • Name your function
    • Select "Custom runtime" for Runtime
    • Create or select an execution role
    • Click "Create function"
  3. Upload the deployment package:

    • In the Function code section, click "Upload from"
    • Select ".zip file"
    • Upload the generated lambda.zip file
    • Click "Save"
  4. Configure the handler:

    • In the Runtime settings section, click "Edit"
    • Set the Handler to Elixir.YourModule.function_name
    • Click "Save"
  5. Test your function:

    • Click "Test"
    • Configure a test event
    • Click "Test" to invoke your function

Performance

Optimizing Cold Starts

  • Increase memory allocation (which also increases CPU power)
  • Minimize dependencies in your application
  • Consider provisioned concurrency for critical functions

Memory and Timeout Configuration

  • Start with at least 512MB of memory for reasonable performance
  • Adjust timeout based on your function's processing needs
  • Monitor execution times to fine-tune these settings

Troubleshooting

Common Issues

  1. Handler Not Found: Ensure the _HANDLER environment variable is correctly set to Elixir.Module.function
  2. Timeout Errors: Check if your function exceeds the Lambda timeout limit
  3. Memory Issues: Increase the Lambda memory allocation if needed
  4. Cold Start Performance: Consider increasing the memory allocation to improve cold start times

Debugging Tips

  • Set LOGLEVEL environment variable to debug for verbose logging
  • Review CloudWatch logs for detailed error information
  • Test locally before deployment when possible

Roadmap

  • Build with Docker Image for x86/arm64
  • Build Locally
  • Create ZIP File
  • CDK Sample
  • HexDocs and Hex.pm publishing
  • GitHub Actions CI/CD templates
  • Performance benchmarks and optimizations
  • Framework integrations (Phoenix, etc.)

Community & Contributing

We welcome contributions of all kinds! Here's how you can help:

  • Bug Reports: Open an issue describing the bug and how to reproduce it
  • Feature Requests: Open an issue describing the desired feature
  • Code Contributions: Submit a pull request with your changes
  • Documentation: Help improve or translate documentation
  • Examples: Share how you're using Mayfly in your projects

Before contributing, please review our:

  • Code of Conduct (link)
  • Contributing Guidelines (link)

License

Mayfly is licensed under the MIT License. See the LICENSE file for details.

About

A lightweight AWS Lambda Custom Runtime for Elixir

Topics

Resources

License

Stars

Watchers

Forks