Skip to content

Selected case studies

Deployable reference systems and product work showing how I approach boundaries, failure modes, infrastructure, and operational trade-offs. Each entry is backed by a public repository containing the full source, so the architecture below can be read directly from the code.

Client → API Gateway REST API → Lambda → DynamoDB

API access management and usage controls

Context. Teams needed a deployable, AWS-native pattern for issuing API keys and binding them to tier-specific throttles and quotas — without standing up a custom rate-limiting service.

Ownership. I designed and implemented the management API, the DynamoDB data model, the IAM boundaries, the usage-plan reconciliation, and the operational documentation.

Key decisions

  • Kept the raw API-key value out of DynamoDB entirely — it is returned to the caller exactly once at creation, and only the API Gateway key id and lifecycle metadata are persisted
  • Modeled keys and plans in a single DynamoDB table with an entity-type GSI (KEY and PLAN entities), keeping access patterns simple and the table on-demand priced
  • Made API Gateway the enforcement point: key validity, throttles, and quotas are checked before the protected workload Lambda is ever invoked, so there is no rate-limiting in application code
  • Scoped the manager's IAM to specific DynamoDB and API Gateway resource ARNs (api keys, usage plans, REST APIs) rather than wildcards, and left the protected workload Lambda with logs-only permissions
  • Treated DynamoDB as the source of truth with usage plans created at runtime by the manager rather than declared in the stack — a deliberate trade-off that keeps the table authoritative at the cost of orphaning runtime keys and plans on stack teardown

Outcome. A focused, security-conscious reference for the full API-key and usage-plan lifecycle — creation, tier binding, runtime updates, and disabling — built on managed AWS controls with least-privilege permissions.

Stack: Python 3.12, API Gateway REST API, Lambda, DynamoDB, IAM, AWS Lambda Powertools v3, Pydantic v2, Serverless Framework

View API management repository

HTTP API → Lambda → SQS → Lambda → SNS → Consumers

Failure-aware asynchronous fan-out

Context. An incoming HTTP request needed to become independently consumable notification and audit events without coupling those consumers to the request path or making the client wait for them.

Ownership. I built the producer, the queue processor, the fan-out topics, the consumer functions, the IAM, and the failure behavior as a single deployable system.

Key decisions

  • Acknowledged the request with HTTP 202 the moment it was safely queued, decoupling client latency from downstream processing
  • Set the queue visibility timeout well above the processor timeout and moved messages to a dead-letter queue after three failed receives, retaining the DLQ for the full SQS maximum so poisoned messages are never silently dropped
  • Used partial batch failure reporting so a single bad record retries itself rather than re-driving the whole batch
  • Scoped every grant to the exact resource it needs (sqs:SendMessage to the processing queue, sns:Publish to the two topics) rather than granting broad queue or topic access
  • Documented propagation, retry, and non-atomic edge cases explicitly so the buffering, dead-letter, and fan-out behavior is read as deliberate operating constraints

Outcome. A reference pipeline with explicit buffering, fan-out, retry, and dead-letter isolation, where every IAM grant is resource-scoped and the failure path is observable rather than silent.

Stack: Python 3.12, API Gateway HTTP API, Lambda, SQS, SNS, IAM, Serverless Framework

View SNS/SQS fan-out repository

Internet → ALB → Private subnets → ECS service

Private container services on AWS

Context. Engineering teams often need to compare Fargate-managed capacity with EC2-backed ECS while preserving the same secure network, delivery, and image-handling model.

Ownership. I created two parallel implementations — Fargate and EC2 — covering networking, IAM, security groups, load balancing, ECR, capacity, and the ECS services.

Key decisions

  • Kept tasks and container instances in private subnets with no public IP, reachable only through an internet-facing ALB whose security group is the sole permitted source to the service
  • Used awsvpc networking with IP-target registration so the ALB talks directly to task ENIs, and added the listener dependency that prevents the classic ALB/ECS creation race
  • Solved the image-before-service chicken-and-egg with a single stack and a CloudFormation condition: the first deploy stands up the VPC, ALB, ECR, and task definition; the second — after the image is pushed — creates the service
  • Kept the application's task role intentionally empty (the API calls no AWS services) and separated it from the execution role that only pulls the image and writes logs
  • Provisioned the EC2 variant with an Auto Scaling group governed by an ECS capacity provider with managed scaling, IMDSv2 required, and SSM-resolved AMIs — a direct comparison against Fargate-managed capacity on identical networking and delivery foundations

Outcome. Two directly comparable, deployable architectures that make the capacity-model trade-off — managed Fargate versus self-managed EC2 with a capacity provider — concrete and operational.

Stack: ECS, Fargate, EC2 Auto Scaling, ECS capacity provider, ECR, ALB, VPC, Docker, Python 3.12 / Flask, Serverless Framework

View Fargate repository View ECS on EC2 repository

Additional public work

Streaming LLM responses from Lambda

Client → Lambda Function URL → Web Adapter → Flask → OpenAI-compatible LLM API

A single Lambda function streams chat-completion responses back to the client token-by-token as Server-Sent Events, using a Lambda Function URL in response-streaming mode and the AWS Lambda Web Adapter to bridge a Flask app onto the stock managed Python runtime.

  • Achieves true response streaming without a custom runtime or container by stacking the Web Adapter with a RESPONSE_STREAM Function URL
  • Forwards only each completion delta as an SSE data frame, ends with the [DONE] sentinel, and pushes errors onto a named event channel rather than the data stream
  • Ships a dependency-free browser tester built on the Fetch ReadableStream API with an AbortController stop button — the endpoint is POST, so EventSource is not an option
  • Documents secret handling through .env and useDotenv, and explicitly warns that the demo endpoint is unauthenticated, so anyone with the URL can invoke it and consume the upstream key

Stack: Python 3.12, Flask, Lambda Function URLs, Lambda Web Adapter, SSE, AWS Lambda Powertools, OpenAI SDK, Serverless Framework

View Lambda streaming repository

AppSync GraphQL authorization patterns

Client → AppSync GraphQL → Lambda resolvers → DynamoDB

Provisioned two managed AppSync GraphQL APIs in one stack, each backed by direct Lambda resolvers performing DynamoDB CRUD, to contrast distinct authentication and authorization strategies.

  • The Todos API exposes full CRUD behind API-key authentication, with IAM as a secondary provider
  • The Orders API uses Cognito User Pools as the primary provider and an API key for reads, while createOrder is gated to authenticated Cognito users through @aws_cognito_user_pools directives plus an in-code identity guard
  • Stamps the caller's Cognito sub as the order's customerId, so record ownership cannot be spoofed by the client
  • Enables AppSync request logging at the ALL level on both APIs alongside structured Lambda logs

Stack: AppSync, Cognito, Lambda, DynamoDB, IAM, Python 3.12, GraphQL, AWS Lambda Powertools, Pydantic, Serverless Framework

View AppSync GraphQL repository

EventBridge routing and decoupled consumers

Client → API Gateway HTTP API → Lambda → EventBridge bus → consumer Lambdas

Built an HTTP-to-EventBridge system where one producer publishes domain events to a custom bus and multiple consumers subscribe through content-based rules.

  • Demonstrates fan-out and content-based routing — an orders event fans out to two consumers, while a notifications event matches a two-field source + detail-type rule
  • Shows the same rule authored two ways: as Serverless eventBridge triggers and as raw CloudFormation rules with SourceArn-scoped invoke permissions
  • Publishes through a Pydantic envelope that supports both a flat default and a caller-controlled source / detail-type / detail payload
  • Keeps IAM tight — the producer can PutEvents only to the one custom bus — and documents at-least-once delivery plus the idempotency, retry, dead-letter, and alarm work needed before production use

Stack: EventBridge, API Gateway HTTP API, Lambda, IAM, Python 3.12, AWS Lambda Powertools, Pydantic, Serverless Framework

View EventBridge repository

Multi-AZ VPC and private DNS

Internet → IGW → public subnets · NAT → private subnets · Route 53 private DNS

Provisioned a two-Availability-Zone VPC with public and private subnets, a cost-optimized single NAT gateway, a bastion host, two private EC2 hosts, and a Route 53 private hosted zone so every host resolves by name from inside the network.

  • Keeps the stack region-portable — Availability Zones via GetAZs and Amazon Linux 2023 AMIs via the AWS SSM public parameter, with no hardcoded IDs
  • Layers access so private hosts accept SSH only from the bastion's security group by reference rather than CIDR, and generates the EC2 key pair as an AWS resource that stores encrypted private material in SSM Parameter Store
  • Defaults to encrypted gp3 EBS on every host and splits the stack into one CloudFormation concern per file for readability
  • Exposes plain stack outputs (VPC, bastion IP, key material path, private DNS names) and flags the open bastion SSH rule as demo-only to lock down before real use

Stack: VPC, EC2, Route 53, NAT Gateway, SSM Parameter Store, CloudFormation (via Serverless Framework)

View networking stack repository

Want to discuss the engineering behind this work?

Email me at [email protected] Return to the portfolio