Back to Blog

Menu Exporter Service: .NET 8 Architecture Deep Dive

Comprehensive technical documentation of production codebase architectures

ArchitectureDocumentationTypeScriptC# .NETNext.jsNode.js
šŸ“š Comprehensive architecture guides

Menu Exporter Service: .NET 8 Architecture Deep Dive

Executive Summary

The menu-exporter is a serverless menu distribution service built with C# and .NET 8 that runs as AWS Lambda functions deployed via Docker containers. This document provides a comprehensive technical explanation of how .NET 8 is used throughout the architecture, from asynchronous message processing to multi-channel menu distribution across POS, kiosk, e-commerce, and partner integrations.


Table of Contents

  1. .NET 8 Runtime Foundation
  2. C# Language and .NET Framework
  3. AWS Lambda with .NET Runtime
  4. Docker Container Deployment
  5. Asynchronous Message Processing
  6. MySQL Integration and Data Retrieval
  7. Multi-Channel Distribution Architecture
  8. Code Structure and Patterns
  9. Deployment and Execution
  10. Performance Optimization
  11. Development Workflow

.NET 8 Runtime Foundation

What is .NET 8 in This Context?

.NET 8 is Microsoft's cross-platform runtime and framework that executes C# code. The menu-exporter is built with .NET 8, which provides:

  • High-performance runtime optimized for cloud workloads
  • Native support for AWS Lambda
  • Container-based deployment capabilities
  • Advanced async/await patterns for I/O operations
  • Built-in dependency injection and configuration management

Why .NET 8 for Menu Export?

  1. Serverless Native: AWS Lambda natively supports .NET 8 runtime with excellent cold start performance
  2. Docker Support: .NET 8 has first-class Docker container support for Lambda deployment
  3. High Performance: Optimized runtime for high-throughput operations
  4. Enterprise-Grade: Type safety, strong typing, and comprehensive tooling
  5. Multi-Channel: Robust async patterns perfect for parallel channel distribution
  6. Business Impact: Contributed to $6B in digital sales (2024) through reliable menu distribution

Runtime Environment

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│   AWS Lambda Execution Environment  │
│                                     │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”   │
│   │   Docker Container           │   │
│   │                             │   │
│   │   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │   │
│   │   │ .NET 8 Runtime        │ │   │
│   │   │                       │ │   │
│   │   │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │   │
│   │   │ │ Compiled C# IL    │ │ │   │
│   │   │ │ (Intermediate     │ │ │   │
│   │   │ │  Language)         │ │ │   │
│   │   │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │   │
│   │   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │   │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

C# Language and .NET Framework

C# Compilation Process

The menu-exporter is written in C# and compiled to Intermediate Language (IL) that runs on the .NET runtime:

// Source: MenuExporter/src/Handlers/ExportHandler.cs (C#)
using Amazon.Lambda.Core;
using Amazon.SQS.Model;
using System.Text.Json;

namespace MenuExporter.Handlers;

public class ExportHandler
{
    private readonly IMenuRepository _menuRepository;
    private readonly IMessagePublisher _messagePublisher;
    
    public ExportHandler(IMenuRepository menuRepository, IMessagePublisher messagePublisher)
    {
        _menuRepository = menuRepository;
        _messagePublisher = messagePublisher;
    }
    
    public async Task<ExportResult> ExportMenuAsync(ExportRequest request, ILambdaContext context)
    {
        // C# async/await for non-blocking I/O
        var menuData = await _menuRepository.GetMenuDataAsync(request.StoreId);
        
        var exportMessage = new ExportMessage
        {
            StoreId = request.StoreId,
            Channel = request.Channel,
            MenuData = menuData,
            Timestamp = DateTime.UtcNow
        };
        
        await _messagePublisher.PublishAsync(exportMessage);
        
        return new ExportResult { Success = true, MessageId = exportMessage.Id };
    }
}

Compilation Process:

dotnet build --configuration Release
# C# → IL (Intermediate Language)
# IL is JIT-compiled to native code at runtime

Runtime Execution:

  1. C# source code compiled to IL (Intermediate Language)
  2. IL packaged in .NET assembly (.dll)
  3. .NET runtime JIT-compiles IL to native code on first execution
  4. Subsequent calls use cached native code for performance

.NET Project Configuration

<!-- MenuExporter.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <OutputType>Library</OutputType>
    <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
  </PropertyGroup>
  
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.Core" Version="2.2.0" />
    <PackageReference Include="Amazon.Lambda.RuntimeSupport" Version="1.8.0" />
    <PackageReference Include="AWSSDK.SQS" Version="3.7.400.0" />
    <PackageReference Include="AWSSDK.SNS" Version="3.7.400.0" />
    <PackageReference Include="MySql.Data" Version="9.0.0" />
    <PackageReference Include="System.Text.Json" Version="8.0.0" />
  </ItemGroup>
</Project>

.NET Module System

The compiled C# uses .NET assemblies (similar to Node.js modules):

// .NET namespace and assembly organization
namespace MenuExporter.Services
{
    public class MenuService
    {
        // Service implementation
    }
}

// Assembly references (similar to Node.js require)
using MenuExporter.Repositories;
using MenuExporter.Models;
using Amazon.Lambda.Core;

AWS Lambda with .NET Runtime

Lambda Function Structure

Each menu export operation is a .NET 8 Lambda function:

// MenuExporter/src/Handlers/LambdaHandler.cs
using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace MenuExporter;

public class Function
{
    private static async Task Main()
    {
        // .NET Lambda bootstrap
        var handler = new ExportHandler(
            new MenuRepository(),
            new SnsMessagePublisher()
        );
        
        await LambdaBootstrapBuilder.Create(handler.HandleAsync, new DefaultLambdaJsonSerializer())
            .Build()
            .RunAsync();
    }
}

public class ExportHandler
{
    public async Task<APIGatewayProxyResponse> HandleAsync(
        APIGatewayProxyRequest request,
        ILambdaContext context)
    {
        // .NET execution context
        context.Logger.LogInformation($"Processing export request: {request.Body}");
        
        try
        {
            // Export logic
            var result = await ProcessExportAsync(request, context);
            return new APIGatewayProxyResponse
            {
                StatusCode = 200,
                Body = JsonSerializer.Serialize(result)
            };
        }
        catch (Exception ex)
        {
            context.Logger.LogError($"Error: {ex.Message}");
            throw;
        }
    }
}

Lambda Runtime Configuration

# serverless.yml
service: menu-exporter

provider:
  name: aws
  runtime: provided.al2  # Custom runtime (Docker)
  memorySize: 1024
  timeout: 300
  region: us-east-1
  environment:
    MYSQL_CONNECTION_STRING: ${ssm:/menu-exporter/mysql-connection}
    SNS_TOPIC_ARN: ${ssm:/menu-exporter/sns-topic}
    SQS_QUEUE_URL: ${ssm:/menu-exporter/sqs-queue}

functions:
  exportMenu:
    image:
      uri: ${aws:accountId}.dkr.ecr.${aws:region}.amazonaws.com/menu-exporter:latest
    events:
      - http:
          path: export
          method: post
      - sqs:
          arn: ${self:custom.sqsQueueArn}
          batchSize: 10

.NET Execution Model

Lambda Invocation Flow:
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  1. Request arrives (HTTP/SQS)     │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
               │
               ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  2. Lambda invokes Docker container │
│     (.NET 8 runtime starts)          │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
               │
               ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  3. .NET loads assembly              │
│     - Load MenuExporter.dll          │
│     - JIT compile IL → native code   │
│     - Execute handler function       │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
               │
               ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  4. Handler executes (async)        │
│     - await MySQL query              │
│     - await SNS/SQS publish          │
│     - Process menu data              │
│     - Return result                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
               │
               ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  5. Result returned                  │
│     - .NET runtime may persist       │
│       for warm start                 │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Cold Start vs Warm Start

Cold Start (.NET initialization):

  1. Lambda service starts Docker container
  2. .NET runtime initializes
  3. Loads assembly (MenuExporter.dll)
  4. JIT compiles IL to native code
  5. Executes handler function
  6. Time: ~500ms-2s (first invocation, depending on container size)

Warm Start (.NET already loaded):

  1. Reuses existing Docker container
  2. .NET runtime already initialized
  3. Assembly already loaded and JIT-compiled
  4. Executes handler function immediately
  5. Time: <50ms (subsequent invocations)

Optimization for High-Throughput:

  • Container image optimization (minimal base image)
  • Provisioned concurrency for critical paths
  • Assembly pre-compilation (ReadyToRun)
  • Connection pooling for MySQL
  • Minimal dependencies to reduce cold start

Docker Container Deployment

Dockerfile Configuration

The menu-exporter uses Docker containers for deployment, enabling consistent execution across POS, kiosk, e-commerce, and partner integrations:

# Dockerfile
FROM public.ecr.aws/lambda/dotnet:8 AS base

WORKDIR /var/task

# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

COPY ["MenuExporter/MenuExporter.csproj", "MenuExporter/"]
RUN dotnet restore "MenuExporter/MenuExporter.csproj"

COPY . .
WORKDIR "/src/MenuExporter"
RUN dotnet build "MenuExporter.csproj" \
    --configuration Release \
    --no-restore \
    -p:PublishReadyToRun=true

# Publish stage
FROM build AS publish
RUN dotnet publish "MenuExporter.csproj" \
    --configuration Release \
    --no-build \
    --output /app/publish \
    -p:PublishReadyToRun=true

# Final stage
FROM base AS final
WORKDIR /var/task

COPY --from=publish /app/publish .

# Lambda handler entry point
CMD ["MenuExporter::MenuExporter.Function::FunctionHandler"]

Container Architecture

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│   AWS Lambda Container Image            │
│                                          │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │
│   │  Amazon Linux 2 Base             │  │
│   │  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │  │
│   │  │ .NET 8 Runtime              │  │  │
│   │  │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │  │  │
│   │  │ │ MenuExporter.dll       │ │  │  │
│   │  │ │ (JIT-compiled native)  │ │  │  │
│   │  │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │  │  │
│   │  │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │  │  │
│   │  │ │ Dependencies           │ │  │  │
│   │  │ │ - AWSSDK.*             │ │  │  │
│   │  │ │ - MySql.Data           │ │  │  │
│   │  │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │  │  │
│   │  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │  │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Multi-Platform Deployment

The Docker container enables deployment across multiple platforms:

// Deployment targets
- POS Systems: In-store point-of-sale terminals
- Kiosks: Self-service ordering kiosks
- E-commerce: Web-based ordering platforms
- Partner Integrations: DoorDash, Grubhub, Uber Eats, etc.

Consistent Execution:

  • Same container image runs identically across all platforms
  • No platform-specific code required
  • Unified deployment pipeline
  • Simplified testing and validation

Asynchronous Message Processing

SNS/SQS Architecture

The menu-exporter uses Amazon SNS (Simple Notification Service) and Amazon SQS (Simple Queue Service) for asynchronous, scalable message processing:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│      Menu Export Request             │
│  (HTTP API or Scheduled Trigger)     │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
               │
               ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│   Lambda Function (.NET 8)           │
│   ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │
│   │  Retrieve menu from MySQL     │  │
│   │  Format for channel           │  │
│   │  Publish to SNS                │  │
│   ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
               │
               ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│      Amazon SNS Topic                │
│  - Fan-out to multiple subscribers   │
│  - Reliable message delivery         │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
               │
               ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
               ā–¼                 ā–¼                 ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  SQS Queue       │  │  SQS Queue       │  │  SQS Queue       │
│  (POS Channel)   │  │  (E-commerce)    │  │  (Partners)      │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
         │                     │                     │
         ā–¼                     ā–¼                     ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Lambda Consumer │  │  Lambda Consumer │  │  Lambda Consumer │
│  (.NET 8)        │  │  (.NET 8)       │  │  (.NET 8)        │
│  Delivers to POS │  │  Delivers to Web │  │  Delivers to APIs │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

SNS Publisher Implementation

// MenuExporter/src/Services/SnsMessagePublisher.cs
using Amazon.SimpleNotificationService;
using Amazon.SimpleNotificationService.Model;
using System.Text.Json;

namespace MenuExporter.Services;

public class SnsMessagePublisher : IMessagePublisher
{
    private readonly IAmazonSimpleNotificationService _snsClient;
    private readonly string _topicArn;
    private readonly ILogger _logger;
    
    public SnsMessagePublisher(
        IAmazonSimpleNotificationService snsClient,
        IConfiguration configuration,
        ILogger<SnsMessagePublisher> logger)
    {
        _snsClient = snsClient;
        _topicArn = configuration["SNS_TOPIC_ARN"];
        _logger = logger;
    }
    
    public async Task<string> PublishAsync(ExportMessage message)
    {
        try
        {
            // .NET async/await for non-blocking SNS publish
            var request = new PublishRequest
            {
                TopicArn = _topicArn,
                Message = JsonSerializer.Serialize(message),
                MessageAttributes = new Dictionary<string, MessageAttributeValue>
                {
                    ["Channel"] = new MessageAttributeValue
                    {
                        DataType = "String",
                        StringValue = message.Channel
                    },
                    ["StoreId"] = new MessageAttributeValue
                    {
                        DataType = "String",
                        StringValue = message.StoreId
                    }
                }
            };
            
            var response = await _snsClient.PublishAsync(request);
            
            _logger.LogInformation(
                "Published menu export message. MessageId: {MessageId}, Channel: {Channel}",
                response.MessageId,
                message.Channel
            );
            
            return response.MessageId;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to publish message to SNS");
            throw;
        }
    }
}

SQS Consumer Implementation

// MenuExporter/src/Handlers/SqsEventHandler.cs
using Amazon.Lambda.Core;
using Amazon.Lambda.SQSEvents;
using Amazon.SQS;
using System.Text.Json;

namespace MenuExporter.Handlers;

public class SqsEventHandler
{
    private readonly IChannelDeliveryService _deliveryService;
    private readonly ILogger _logger;
    
    public SqsEventHandler(
        IChannelDeliveryService deliveryService,
        ILogger<SqsEventHandler> logger)
    {
        _deliveryService = deliveryService;
        _logger = logger;
    }
    
    public async Task<SQSBatchResponse> HandleAsync(
        SQSEvent sqsEvent,
        ILambdaContext context)
    {
        var batchItemFailures = new List<SQSBatchResponse.BatchItemFailure>();
        
        // Process SQS records in parallel using .NET async
        var tasks = sqsEvent.Records.Select(async record =>
        {
            try
            {
                var message = JsonSerializer.Deserialize<ExportMessage>(record.Body);
                
                // Deliver to specific channel
                await _deliveryService.DeliverAsync(message, context);
                
                _logger.LogInformation(
                    "Successfully processed message. MessageId: {MessageId}",
                    record.MessageId
                );
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to process message: {MessageId}", record.MessageId);
                
                // Return failed message for retry
                batchItemFailures.Add(new SQSBatchResponse.BatchItemFailure
                {
                    ItemIdentifier = record.MessageId
                });
            }
        });
        
        await Task.WhenAll(tasks);
        
        return new SQSBatchResponse
        {
            BatchItemFailures = batchItemFailures
        };
    }
}

Message Processing Flow

// High-throughput parallel processing
public async Task ProcessBatchAsync(List<ExportMessage> messages)
{
    // .NET parallel processing for high throughput
    var semaphore = new SemaphoreSlim(10); // Limit concurrent operations
    var tasks = messages.Select(async message =>
    {
        await semaphore.WaitAsync();
        try
        {
            await ProcessSingleExportAsync(message);
        }
        finally
        {
            semaphore.Release();
        }
    });
    
    await Task.WhenAll(tasks);
}

MySQL Integration and Data Retrieval

MySQL Connection Management

The service integrates with MySQL for menu data retrieval:

// MenuExporter/src/Repositories/MenuRepository.cs
using MySql.Data.MySqlClient;
using System.Data;
using Dapper;

namespace MenuExporter.Repositories;

public class MenuRepository : IMenuRepository
{
    private readonly string _connectionString;
    private readonly ILogger<MenuRepository> _logger;
    
    public MenuRepository(IConfiguration configuration, ILogger<MenuRepository> logger)
    {
        _connectionString = configuration.GetConnectionString("MySQL");
        _logger = logger;
    }
    
    public async Task<MenuData> GetMenuDataAsync(string storeId)
    {
        using var connection = new MySqlConnection(_connectionString);
        
        try
        {
            // .NET async database query
            var menuData = await connection.QueryFirstOrDefaultAsync<MenuData>(
                @"
                    SELECT 
                        m.id,
                        m.store_id,
                        m.product_id,
                        m.name,
                        m.description,
                        m.price,
                        m.availability,
                        m.category_id,
                        c.name as category_name
                    FROM menus m
                    INNER JOIN categories c ON m.category_id = c.id
                    WHERE m.store_id = @StoreId
                    AND m.is_active = 1
                    ORDER BY c.display_order, m.display_order",
                new { StoreId = storeId }
            );
            
            if (menuData == null)
            {
                _logger.LogWarning("No menu data found for store: {StoreId}", storeId);
                return new MenuData { StoreId = storeId, Items = new List<MenuItem>() };
            }
            
            // Load related items
            menuData.Items = await GetMenuItemsAsync(connection, storeId);
            
            return menuData;
        }
        catch (MySqlException ex)
        {
            _logger.LogError(ex, "MySQL error retrieving menu for store: {StoreId}", storeId);
            throw;
        }
    }
    
    private async Task<List<MenuItem>> GetMenuItemsAsync(
        MySqlConnection connection,
        string storeId)
    {
        return (await connection.QueryAsync<MenuItem>(
            @"
                SELECT 
                    id,
                    product_id,
                    name,
                    description,
                    price,
                    availability,
                    category_id
                FROM menu_items
                WHERE store_id = @StoreId
                AND is_active = 1",
            new { StoreId = storeId }
        )).ToList();
    }
}

Connection Pooling

// Optimized connection string with pooling
var connectionString = new MySqlConnectionStringBuilder
{
    Server = mysqlHost,
    Database = mysqlDatabase,
    UserID = mysqlUser,
    Password = mysqlPassword,
    MinimumPoolSize = 5,      // Maintain 5 connections
    MaximumPoolSize = 20,     // Allow up to 20 connections
    ConnectionTimeout = 30,
    DefaultCommandTimeout = 30,
    Pooling = true            // Enable connection pooling
}.ConnectionString;

Data Transformation

// Transform MySQL data for channel-specific formats
public class MenuTransformer
{
    public ChannelMenu TransformForChannel(MenuData menuData, string channel)
    {
        return channel switch
        {
            "pos" => TransformForPos(menuData),
            "kiosk" => TransformForKiosk(menuData),
            "ecommerce" => TransformForEcommerce(menuData),
            "doordash" => TransformForDoorDash(menuData),
            "grubhub" => TransformForGrubhub(menuData),
            _ => TransformDefault(menuData)
        };
    }
    
    private ChannelMenu TransformForPos(MenuData menuData)
    {
        // POS-specific formatting
        return new ChannelMenu
        {
            StoreId = menuData.StoreId,
            Format = "pos",
            Items = menuData.Items
                .Where(item => item.Availability)
                .Select(item => new PosMenuItem
                {
                    ProductId = item.ProductId,
                    Name = item.Name,
                    Price = item.Price,
                    Category = item.CategoryName
                })
                .ToList()
        };
    }
    
    // Similar transformations for other channels...
}

Multi-Channel Distribution Architecture

Channel-Specific Delivery

The service distributes menu data to multiple channels with channel-specific formatting:

// MenuExporter/src/Services/ChannelDeliveryService.cs
namespace MenuExporter.Services;

public interface IChannelDeliveryService
{
    Task DeliverAsync(ExportMessage message, ILambdaContext context);
}

public class ChannelDeliveryService : IChannelDeliveryService
{
    private readonly Dictionary<string, IChannelHandler> _channelHandlers;
    private readonly ILogger<ChannelDeliveryService> _logger;
    
    public ChannelDeliveryService(
        IEnumerable<IChannelHandler> channelHandlers,
        ILogger<ChannelDeliveryService> logger)
    {
        _channelHandlers = channelHandlers.ToDictionary(h => h.ChannelName);
        _logger = logger;
    }
    
    public async Task DeliverAsync(ExportMessage message, ILambdaContext context)
    {
        if (!_channelHandlers.TryGetValue(message.Channel, out var handler))
        {
            _logger.LogWarning("No handler found for channel: {Channel}", message.Channel);
            throw new ArgumentException($"Unknown channel: {message.Channel}");
        }
        
        try
        {
            await handler.DeliverAsync(message, context);
            _logger.LogInformation(
                "Successfully delivered menu to {Channel} for store {StoreId}",
                message.Channel,
                message.StoreId
            );
        }
        catch (Exception ex)
        {
            _logger.LogError(
                ex,
                "Failed to deliver menu to {Channel} for store {StoreId}",
                message.Channel,
                message.StoreId
            );
            throw;
        }
    }
}

Channel Handlers

// POS Channel Handler
public class PosChannelHandler : IChannelHandler
{
    public string ChannelName => "pos";
    
    private readonly IPosApiClient _posApiClient;
    
    public async Task DeliverAsync(ExportMessage message, ILambdaContext context)
    {
        var posMenu = TransformToPosFormat(message.MenuData);
        await _posApiClient.UpdateMenuAsync(message.StoreId, posMenu);
    }
}

// DoorDash Channel Handler
public class DoorDashChannelHandler : IChannelHandler
{
    public string ChannelName => "doordash";
    
    private readonly IDoorDashApiClient _doorDashClient;
    
    public async Task DeliverAsync(ExportMessage message, ILambdaContext context)
    {
        var doorDashMenu = TransformToDoorDashFormat(message.MenuData);
        await _doorDashClient.SyncMenuAsync(message.StoreId, doorDashMenu);
    }
}

// E-commerce Channel Handler
public class EcommerceChannelHandler : IChannelHandler
{
    public string ChannelName => "ecommerce";
    
    private readonly IEcommerceApiClient _ecommerceClient;
    
    public async Task DeliverAsync(ExportMessage message, ILambdaContext context)
    {
        var webMenu = TransformToWebFormat(message.MenuData);
        await _ecommerceClient.PublishMenuAsync(message.StoreId, webMenu);
    }
}

Parallel Channel Processing

// Process multiple channels in parallel
public async Task ExportToAllChannelsAsync(string storeId, MenuData menuData)
{
    var channels = new[] { "pos", "kiosk", "ecommerce", "doordash", "grubhub" };
    
    // .NET parallel async processing
    var tasks = channels.Select(async channel =>
    {
        try
        {
            var message = new ExportMessage
            {
                StoreId = storeId,
                Channel = channel,
                MenuData = menuData
            };
            
            await _channelDeliveryService.DeliverAsync(message, _lambdaContext);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to export to channel {Channel}", channel);
            // Continue with other channels even if one fails
        }
    });
    
    await Task.WhenAll(tasks);
}

Code Structure and Patterns

Project Structure

menu-exporter/
ā”œā”€ā”€ src/
│   ā”œā”€ā”€ Handlers/              # Lambda handlers (.NET)
│   │   ā”œā”€ā”€ ExportHandler.cs
│   │   ā”œā”€ā”€ SqsEventHandler.cs
│   │   └── ScheduledHandler.cs
│   ā”œā”€ā”€ Services/              # Business logic (.NET)
│   │   ā”œā”€ā”€ ChannelDeliveryService.cs
│   │   ā”œā”€ā”€ MenuTransformer.cs
│   │   └── SnsMessagePublisher.cs
│   ā”œā”€ā”€ Repositories/          # Data access (.NET)
│   │   ā”œā”€ā”€ MenuRepository.cs
│   │   └── IMenuRepository.cs
│   ā”œā”€ā”€ Models/                # Data models
│   │   ā”œā”€ā”€ MenuData.cs
│   │   ā”œā”€ā”€ ExportMessage.cs
│   │   └── ChannelMenu.cs
│   ā”œā”€ā”€ Clients/               # External API clients
│   │   ā”œā”€ā”€ PosApiClient.cs
│   │   ā”œā”€ā”€ DoorDashApiClient.cs
│   │   └── GrubhubApiClient.cs
│   └── Infrastructure/        # Configuration
│       ā”œā”€ā”€ DependencyInjection.cs
│       └── Configuration.cs
ā”œā”€ā”€ tests/
│   ā”œā”€ā”€ Handlers.Tests/
│   ā”œā”€ā”€ Services.Tests/
│   └── Repositories.Tests/
ā”œā”€ā”€ Dockerfile
ā”œā”€ā”€ serverless.yml
ā”œā”€ā”€ MenuExporter.csproj
└── Program.cs

.NET Patterns Used

1. Dependency Injection

// Infrastructure/DependencyInjection.cs
using Microsoft.Extensions.DependencyInjection;
using Amazon.SimpleNotificationService;
using Amazon.SQS;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMenuExporterServices(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // AWS SDK clients
        services.AddAWSService<IAmazonSimpleNotificationService>();
        services.AddAWSService<IAmazonSQS>();
        
        // Application services
        services.AddScoped<IMenuRepository, MenuRepository>();
        services.AddScoped<IMessagePublisher, SnsMessagePublisher>();
        services.AddScoped<IChannelDeliveryService, ChannelDeliveryService>();
        
        // Channel handlers
        services.AddScoped<IChannelHandler, PosChannelHandler>();
        services.AddScoped<IChannelHandler, DoorDashChannelHandler>();
        services.AddScoped<IChannelHandler, GrubhubChannelHandler>();
        services.AddScoped<IChannelHandler, EcommerceChannelHandler>();
        
        return services;
    }
}

2. Async/Await Pattern

// .NET async/await for non-blocking I/O
public async Task<ExportResult> ExportMenuAsync(ExportRequest request)
{
    // Non-blocking MySQL query
    var menuData = await _menuRepository.GetMenuDataAsync(request.StoreId);
    
    // Non-blocking SNS publish
    var messageId = await _messagePublisher.PublishAsync(menuData);
    
    return new ExportResult { Success = true, MessageId = messageId };
}

3. Error Handling and Retry

// .NET error handling with retry logic
public async Task<T> ExecuteWithRetryAsync<T>(
    Func<Task<T>> operation,
    int maxRetries = 3)
{
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            return await operation();
        }
        catch (Exception ex) when (attempt < maxRetries)
        {
            _logger.LogWarning(
                "Operation failed (attempt {Attempt}/{MaxRetries}): {Error}",
                attempt,
                maxRetries,
                ex.Message
            );
            
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
        }
    }
    
    throw new InvalidOperationException("Operation failed after all retries");
}

4. Configuration Management

// .NET configuration pattern
public class MenuExporterConfiguration
{
    public string MySqlConnectionString { get; set; }
    public string SnsTopicArn { get; set; }
    public string SqsQueueUrl { get; set; }
    public Dictionary<string, string> ChannelEndpoints { get; set; }
    public int MaxConcurrentExports { get; set; } = 10;
    public int RetryAttempts { get; set; } = 3;
}

// Load from environment/SSM
var configuration = new ConfigurationBuilder()
    .AddEnvironmentVariables()
    .AddSystemsManager("/menu-exporter/")
    .Build()
    .Get<MenuExporterConfiguration>();

5. Logging and Observability

// .NET structured logging
public class ExportHandler
{
    private readonly ILogger<ExportHandler> _logger;
    
    public async Task HandleAsync(ExportRequest request)
    {
        using var scope = _logger.BeginScope(new Dictionary<string, object>
        {
            ["StoreId"] = request.StoreId,
            ["Channel"] = request.Channel,
            ["RequestId"] = request.RequestId
        });
        
        _logger.LogInformation("Starting menu export");
        
        try
        {
            var result = await ProcessExportAsync(request);
            _logger.LogInformation(
                "Menu export completed successfully. Exported {ItemCount} items",
                result.ItemCount
            );
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Menu export failed");
            throw;
        }
    }
}

Deployment and Execution

Build Process

# 1. Restore .NET dependencies
dotnet restore

# 2. Build C# project
dotnet build --configuration Release

# 3. Publish for Lambda (ReadyToRun for faster cold starts)
dotnet publish \
  --configuration Release \
  --runtime linux-x64 \
  -p:PublishReadyToRun=true \
  --output ./publish

# 4. Build Docker image
docker build -t menu-exporter:latest .

# 5. Tag and push to ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com

docker tag menu-exporter:latest \
  ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/menu-exporter:latest

docker push ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/menu-exporter:latest

# 6. Deploy with Serverless Framework
serverless deploy

Serverless Framework Configuration

# serverless.yml
service: menu-exporter

provider:
  name: aws
  runtime: provided.al2
  region: us-east-1
  memorySize: 1024
  timeout: 300
  environment:
    ASPNETCORE_ENVIRONMENT: Production
    MYSQL_CONNECTION_STRING: ${ssm:/menu-exporter/mysql-connection~true}
    SNS_TOPIC_ARN: ${ssm:/menu-exporter/sns-topic}
    SQS_QUEUE_URL: ${ssm:/menu-exporter/sqs-queue}

functions:
  exportMenu:
    image:
      uri: ${aws:accountId}.dkr.ecr.${aws:region}.amazonaws.com/menu-exporter:${env:IMAGE_TAG, 'latest'}
    events:
      - http:
          path: export
          method: post
          cors: true
      - schedule:
          rate: rate(5 minutes)
          enabled: true
      - sqs:
          arn: ${self:custom.sqsQueueArn}
          batchSize: 10
          maximumBatchingWindowInSeconds: 5

resources:
  Resources:
    MenuExportSnsTopic:
      Type: AWS::SNS::Topic
      Properties:
        TopicName: menu-export-topic
        DisplayName: Menu Export Topic
        
    MenuExportSqsQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: menu-export-queue
        VisibilityTimeout: 300
        MessageRetentionPeriod: 1209600
        ReceiveMessageWaitTimeSeconds: 20

Execution Flow

1. Developer writes C# code
   ↓
2. C# compiles to IL (Intermediate Language)
   ↓
3. IL packaged in .NET assembly
   ↓
4. Docker image built with .NET 8 runtime
   ↓
5. Image pushed to ECR
   ↓
6. Serverless Framework deploys Lambda
   ↓
7. Lambda uses Docker container (.NET 8)
   ↓
8. Export request arrives (HTTP/SQS/Schedule)
   ↓
9. .NET runtime executes handler
   ↓
10. Handler queries MySQL (async)
   ↓
11. Handler publishes to SNS/SQS
   ↓
12. Result returned

Performance Optimization

.NET-Specific Optimizations

1. ReadyToRun Compilation

<!-- MenuExporter.csproj -->
<PropertyGroup>
  <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>

Benefits:

  • Pre-compiled native code (faster cold starts)
  • Reduced JIT compilation overhead
  • Smaller memory footprint

2. Connection Pooling

// MySQL connection pooling
var connectionString = new MySqlConnectionStringBuilder
{
    MinimumPoolSize = 5,
    MaximumPoolSize = 20,
    Pooling = true
}.ConnectionString;

// Reuse connections across Lambda invocations (warm containers)
private static MySqlConnection _sharedConnection;

3. Async Concurrency

// .NET parallel processing for high throughput
public async Task ProcessBatchAsync(List<ExportMessage> messages)
{
    var semaphore = new SemaphoreSlim(10); // Limit concurrent operations
    
    var tasks = messages.Select(async message =>
    {
        await semaphore.WaitAsync();
        try
        {
            await ProcessExportAsync(message);
        }
        finally
        {
            semaphore.Release();
        }
    });
    
    await Task.WhenAll(tasks);
}

4. Memory Management

// .NET garbage collection optimization
public async Task<ExportResult> ProcessExportAsync(ExportRequest request)
{
    using var scope = _serviceProvider.CreateScope();
    var handler = scope.ServiceProvider.GetRequiredService<ExportHandler>();
    
    var result = await handler.HandleAsync(request);
    
    // Explicit disposal helps GC
    scope.Dispose();
    
    return result;
}

5. Container Image Optimization

# Multi-stage build for smaller image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MenuExporter.csproj", "./"]
RUN dotnet restore "MenuExporter.csproj"
COPY . .
RUN dotnet build "MenuExporter.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "MenuExporter.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MenuExporter.dll"]

High-Throughput Optimization

Achieved through:

  1. Asynchronous message processing (SNS/SQS)
  2. Parallel channel distribution (Task.WhenAll)
  3. Connection pooling (MySQL)
  4. Container optimization (smaller images, faster cold starts)
  5. ReadyToRun compilation (pre-compiled native code)
  6. Batch processing (SQS batch operations)

Development Workflow

Local Development (.NET)

# 1. Install .NET 8 SDK
dotnet --version  # 8.0.x

# 2. Restore dependencies
dotnet restore

# 3. Run tests
dotnet test

# 4. Run locally
dotnet run --project MenuExporter

# 5. Test Lambda function locally
sam local invoke ExportMenuFunction \
  --event events/export-request.json \
  --docker-network host

# 6. Build and test Docker container
docker build -t menu-exporter:local .
docker run -p 9000:8080 menu-exporter:local

Testing .NET Code

// tests/Handlers.Tests/ExportHandlerTests.cs
using Xunit;
using Moq;
using MenuExporter.Handlers;
using MenuExporter.Repositories;
using MenuExporter.Services;

public class ExportHandlerTests
{
    [Fact]
    public async Task HandleAsync_ValidRequest_ReturnsSuccess()
    {
        // Arrange
        var mockRepository = new Mock<IMenuRepository>();
        var mockPublisher = new Mock<IMessagePublisher>();
        var handler = new ExportHandler(mockRepository.Object, mockPublisher.Object);
        
        var request = new ExportRequest
        {
            StoreId = "12345",
            Channel = "pos"
        };
        
        mockRepository
            .Setup(r => r.GetMenuDataAsync("12345"))
            .ReturnsAsync(new MenuData { StoreId = "12345" });
        
        mockPublisher
            .Setup(p => p.PublishAsync(It.IsAny<ExportMessage>()))
            .ReturnsAsync("message-id-123");
        
        // Act
        var result = await handler.HandleAsync(request, Mock.Of<ILambdaContext>());
        
        // Assert
        Assert.True(result.Success);
        Assert.Equal("message-id-123", result.MessageId);
        mockRepository.Verify(r => r.GetMenuDataAsync("12345"), Times.Once);
        mockPublisher.Verify(p => p.PublishAsync(It.IsAny<ExportMessage>()), Times.Once);
    }
}

Debugging .NET Lambda

// Enable .NET logging and debugging
public class ExportHandler
{
    private readonly ILogger<ExportHandler> _logger;
    
    public async Task HandleAsync(ExportRequest request, ILambdaContext context)
    {
        // .NET structured logging
        _logger.LogInformation(
            "Processing export request. StoreId: {StoreId}, Channel: {Channel}",
            request.StoreId,
            request.Channel
        );
        
        try
        {
            var result = await ProcessExportAsync(request);
            _logger.LogInformation("Export completed successfully");
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Export failed");
            throw;
        }
    }
}

CI/CD Pipeline

# .github/workflows/deploy.yml
name: Deploy Menu Exporter

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '8.0.x'
      
      - name: Restore dependencies
        run: dotnet restore
      
      - name: Build
        run: dotnet build --configuration Release --no-restore
      
      - name: Test
        run: dotnet test --no-build --configuration Release --verbosity normal
      
      - name: Publish
        run: dotnet publish --configuration Release -p:PublishReadyToRun=true
      
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
      
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1
      
      - name: Build and push Docker image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/menu-exporter:$IMAGE_TAG .
          docker push $ECR_REGISTRY/menu-exporter:$IMAGE_TAG
      
      - name: Deploy with Serverless
        run: |
          npm install -g serverless
          serverless deploy --image-tag ${{ github.sha }}

Summary

The menu-exporter is built entirely on .NET 8 and C# through:

  1. C# → IL compilation for .NET runtime
  2. AWS Lambda with .NET 8 runtime in Docker containers
  3. Asynchronous message processing via SNS/SQS
  4. MySQL integration for menu data retrieval
  5. Multi-channel distribution across POS, kiosk, e-commerce, and partners
  6. Docker containerization for consistent deployment
  7. High-throughput optimization through async patterns and parallel processing

The service demonstrates production-grade .NET expertise at enterprise scale:

  • $6B in digital sales (2024) enabled through reliable menu distribution
  • High-throughput menu exports with asynchronous processing
  • Multi-channel support (POS, kiosk, e-commerce, DoorDash, Grubhub, etc.)
  • Robust scalability through SNS/SQS message queuing
  • Container-based deployment for consistent execution across platforms

This architecture showcases deep understanding of .NET 8 runtime characteristics, serverless execution models, Docker containerization, and high-performance C# development patterns for enterprise-scale menu distribution systems.