Back to Articles

Aegis: The Go Lambda Framework That Treats One Function Like Ten

[ View on GitHub ]

Aegis: The Go Lambda Framework That Treats One Function Like Ten

Hook

Most serverless frameworks force you to deploy separate Lambda functions for each event type. Aegis takes the opposite bet: what if one intelligently-routed function could replace an entire constellation of single-purpose handlers?

Context

The early serverless movement promised infrastructure liberation, but quickly revealed a new form of complexity. By 2017, teams building on AWS Lambda faced a paradox: while individual functions stayed simple, the orchestration layer exploded. A typical microservice might deploy 15+ Lambda functions—one for each API endpoint, another for S3 uploads, separate handlers for scheduled tasks, dedicated functions for inter-service communication. Each function brought its own configuration file, IAM role, CloudWatch log group, and deployment artifact.

This proliferation created cognitive overhead. Developers spent more time managing YAML than writing business logic. Cold start penalties multiplied. Debugging required correlating logs across dozens of functions. The promise of "just write code" collided with the reality of distributed systems management. Aegis emerged from this frustration with a contrarian premise: the AWS Lambda execution model doesn't require one-function-per-trigger. Instead, it offers a router-first framework that lets a single Lambda multiplex across HTTP requests, S3 events, scheduled invocations, and RPC calls—combined with a deployment CLI that treats this pattern as the default.

Technical Insight

Aegis's core architectural decision revolves around event source polymorphism. Rather than forcing developers to write separate handlers for API Gateway, S3, CloudWatch Events, and SNS triggers, it provides a unified router that inspects the incoming event payload and dispatches to the appropriate handler function. This design mirrors traditional web frameworks like Gorilla or Chi, but operates at the AWS event level rather than just HTTP.

Here's how it looks in practice. A single Lambda function can simultaneously serve as a REST API, process S3 uploads, and execute scheduled tasks:

package main

import (
    "github.com/tmaiaroto/aegis/framework"
)

func main() {
    router := framework.New()
    
    // HTTP routes via API Gateway
    router.GET("/users/:id", func(ctx *framework.Context) error {
        userID := ctx.Param("id")
        return ctx.JSON(200, map[string]string{"user": userID})
    })
    
    // S3 event handler
    router.S3("ObjectCreated:*", func(ctx *framework.Context, evt framework.S3Event) error {
        for _, record := range evt.Records {
            bucket := record.S3.Bucket.Name
            key := record.S3.Object.Key
            // Process uploaded file
            return processUpload(bucket, key)
        }
        return nil
    })
    
    // Scheduled task (cron expression in aegis.yaml)
    router.Scheduled("cleanup", func(ctx *framework.Context) error {
        return runDailyCleanup()
    })
    
    router.Listen()
}

The router inspects the raw Lambda event structure and routes accordingly. When API Gateway invokes the function, the event contains requestContext and httpMethod fields—Aegis detects these and routes through the HTTP handler tree. S3 events arrive with a Records array containing s3 objects, triggering S3-specific routes. CloudWatch Events include a detail-type field that maps to scheduled handlers.

This multiplexing happens with minimal performance overhead because Go's type assertions are compile-time optimized, and the event inspection uses simple field presence checks rather than full deserialization. The router also provides context injection, giving handlers access to AWS SDK clients, environment variables, and request metadata through a unified interface.

The deployment side complements this with an aegis.yaml configuration that treats multi-trigger functions as first-class citizens:

app_name: my-service
functions:
  - name: main-handler
    handler: main
    memory: 512
    timeout: 30
    http:
      - path: /users/{id}
        method: GET
      - path: /orders
        method: POST
    events:
      - s3:
          bucket: uploads-bucket
          events:
            - s3:ObjectCreated:*
      - schedule:
          name: cleanup
          expression: rate(1 day)

Running aegis deploy parses this configuration, compiles the Go binary with appropriate build tags (GOOS=linux GOARCH=amd64), creates or updates the Lambda function, provisions API Gateway routes, sets up S3 event notifications, and creates CloudWatch Event rules—all from a single command. The tool handles IAM role creation with least-privilege policies based on declared event sources, though you can override with custom roles.

The framework also includes an RPC mechanism for inter-Lambda communication that bypasses API Gateway overhead. You can invoke another Aegis-powered Lambda directly using the Invoke helper, which serializes arguments, calls the target function, and deserializes responses—effectively creating a typed remote procedure call within your VPC:

response, err := framework.Invoke("other-function", map[string]interface{}{
    "action": "process",
    "data": payload,
})

This RPC pattern emerged from the recognition that many microservice interactions don't need HTTP's ceremony. When one Lambda needs to trigger another, direct invocation through the AWS SDK is faster and cheaper than routing through API Gateway or SQS. Aegis wraps this with convenience methods while maintaining the router pattern on the receiving end.

Gotcha

Aegis makes a deliberate trade-off that becomes painful at scale: it prioritizes developer velocity over infrastructure control. The CLI doesn't manage auxiliary resources like DynamoDB tables, SQS queues, or VPC configurations. You'll need Terraform, CloudFormation, or the AWS CDK for anything beyond Lambda functions and their immediate triggers. This works fine for simple services, but creates a bifurcated workflow when your architecture grows—part Aegis, part something else. Teams often start with Aegis for rapid prototyping, then hit a wall when they need to add databases or message queues, forcing a migration to more comprehensive tooling.

The router pattern also introduces subtle debugging challenges. When a single function handles multiple event types, CloudWatch logs intermingle all invocations. Filtering becomes critical but cumbersome. A failed S3 handler doesn't immediately reveal itself in metrics—you see overall function errors, requiring log diving to identify which route failed. The framework lacks built-in observability hooks for distributed tracing tools like X-Ray, though you can add instrumentation manually. Additionally, the project's maintenance cadence raises concerns. The repository hasn't seen significant updates since 2019, and the Travis CI badge suggests an older CI/CD approach. While the core functionality remains solid for its intended use case, relying on unmaintained dependencies in production serverless deployments carries risk, especially as AWS evolves its Lambda runtime and API Gateway capabilities.

Verdict

Use Aegis if you're building lightweight Go microservices that genuinely benefit from the router pattern—services where multiple event types converge on shared business logic, like an image processing service that accepts uploads via S3, HTTP API, and scheduled batch jobs. It excels for small teams moving fast on well-scoped problems where the infrastructure surface area stays small. The reduced boilerplate and unified handler pattern can significantly accelerate development when your service maps cleanly to Aegis's opinionated structure. Skip it if you need production-grade infrastructure management, active maintenance guarantees, or comprehensive AWS service integration. For greenfield projects with growth potential, start with AWS SAM or the CDK—they require more upfront configuration but won't force architectural rewrites when you outgrow basic Lambda-plus-API-Gateway patterns. If you're specifically drawn to the router concept, consider implementing a lightweight version yourself using the official aws-lambda-go library, giving you pattern benefits without dependency on an under-maintained framework.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/tmaiaroto-aegis.svg)](https://starlog.is/api/badge-click/developer-tools/tmaiaroto-aegis)