C#에서 Lambda 함수 핸들러 정의
C#에서 Lambda 함수 핸들러 정의
Lambda 함수 핸들러는 함수 코드에서 이벤트를 처리하는 메서드입니다. 함수가 호출되면 Lambda가 핸들러 메서드를 실행하며, 함수는 핸들러가 응답을 반환하거나, 종료되거나, 타임아웃될 때까지 실행됩니다. 이 문서는 C#에서 Lambda 함수 핸들러를 사용해 .NET 관리형 런타임과 함께 작업하는 방법(프로젝트 설정, 명명 규칙, 모범 사례 옵션)을 다루고, 주문 정보를 받아 텍스트 파일 영수증을 만들어 Amazon Simple Storage Service(S3) 버킷에 저장하는 C# Lambda 함수 예제를 제공합니다.
본문
이 페이지의 내용은 다음과 같습니다.
- C# 핸들러 프로젝트 설정
- 예제 C# Lambda 함수 코드
- 클래스 라이브러리 핸들러
- 실행 가능 어셈블리 핸들러
- C# 함수의 유효한 핸들러 시그니처
- 핸들러 명명 규칙
- C# Lambda 함수에서의 직렬화
- 파일 기반 함수
- Lambda 컨텍스트 객체 접근 및 사용
- 핸들러에서 SDK for .NET v3 사용
- 환경 변수 접근
- 전역 상태(global state) 사용
- Lambda Annotations 프레임워크로 함수 코드 단순화
- C# Lambda 함수의 코드 모범 사례
C# 핸들러 프로젝트 설정
C#에서 Lambda 함수를 쓸 때는 코드를 작성한 뒤 코드를 Lambda에 배포하는 과정을 거칩니다. .NET에서 Lambda 함수를 배포하는 데는 두 가지 실행 모델이 있습니다: 클래스 라이브러리(class library) 접근 방식과 실행 가능 어셈블리(executable assembly) 접근 방식입니다.
클래스 라이브러리 접근 방식에서는 함수 코드를 .NET 어셈블리(.dll)로 패키징해 .NET 관리형 런타임(dotnet8)과 함께 Lambda에 배포합니다. 핸들러 이름에 대해 Lambda는 AssemblyName::Namespace.Classname::Methodname 형식의 문자열을 기대합니다. 함수의 초기화 단계에서 함수의 클래스가 초기화되고 생성자의 코드가 실행됩니다.
실행 가능 어셈블리 접근 방식에서는 C# 9에서 처음 소개된 최상위 문(top-level statements) 기능을 사용합니다. 이 접근 방식은 Lambda가 함수에 대한 invoke 명령을 받을 때마다 실행하는 실행 가능 어셈블리를 생성합니다. 이 접근 방식에서도 .NET 관리형 런타임(dotnet8)을 사용합니다. 핸들러 이름에는 Lambda에 실행할 실행 가능 어셈블리 이름을 제공합니다.
이 페이지의 메인 예제는 클래스 라이브러리 접근 방식을 보여줍니다. C# Lambda 프로젝트는 여러 방식으로 초기화할 수 있지만 가장 쉬운 방법은 Amazon.Lambda.Tools CLI와 함께 .NET CLI를 사용하는 것입니다. 'Setting up your .NET development environment' 문서의 단계에 따라 Amazon.Lambda.Tools CLI를 설정한 뒤 다음 명령으로 프로젝트를 초기화합니다.
dotnet new lambda.EmptyFunction --name ExampleCS
이 명령은 다음 파일 구조를 생성합니다.
/project-root
└ src
└ ExampleCS
└ Function.cs (contains main handler)
└ Readme.md
└ aws-lambda-tools-defaults.json
└ ExampleCS.csproj
└ test
└ ExampleCS.Tests
└ FunctionTest.cs (contains main handler)
└ ExampleCS.Tests.csproj
이 파일 구조에서 함수의 메인 핸들러 로직은 Function.cs 파일에 있습니다.
예제 C# Lambda 함수 코드
다음 예제 C# Lambda 함수 코드는 주문 정보를 받아 텍스트 파일 영수증을 만들고 이를 Amazon S3 버킷에 저장합니다.
using System;
using System.Text;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Amazon.S3;
using Amazon.S3.Model;
// Assembly attribute to enable Lambda function logging
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace ExampleLambda;
public class Order
{
public string OrderId { get; set; } = string.Empty;
public double Amount { get; set; }
public string Item { get; set; } = string.Empty;
}
public class OrderHandler
{
private static readonly AmazonS3Client s3Client = new();
public async Task<string> HandleRequest(Order order, ILambdaContext context)
{
try
{
string? bucketName = Environment.GetEnvironmentVariable("RECEIPT_BUCKET");
if (string.IsNullOrWhiteSpace(bucketName))
{
throw new ArgumentException("RECEIPT_BUCKET environment variable is not set");
}
string receiptContent = $"OrderID: {order.OrderId}\nAmount: ${order.Amount:F2}\nItem: {order.Item}";
string key = $"receipts/{order.OrderId}.txt";
await UploadReceiptToS3(bucketName, key, receiptContent);
context.Logger.LogInformation($"Successfully processed order {order.OrderId} and stored receipt in S3 bucket {bucketName}");
return "Success";
}
catch (Exception ex)
{
context.Logger.LogError($"Failed to process order: {ex.Message}");
throw;
}
}
private async Task UploadReceiptToS3(string bucketName, string key, string receiptContent)
{
try
{
var putRequest = new PutObjectRequest
{
BucketName = bucketName,
Key = key,
ContentBody = receiptContent,
ContentType = "text/plain"
};
await s3Client.PutObjectAsync(putRequest);
}
catch (AmazonS3Exception ex)
{
throw new Exception($"Failed to upload receipt to S3: {ex.Message}", ex);
}
}
}
이 Function.cs 파일에는 다음의 코드 섹션이 포함됩니다.
using문: Lambda 함수에 필요한 C# 클래스를 import 하는 데 사용합니다.[assembly: LambdaSerializer(...)]:LambdaSerializer는 Lambda가 JSON 이벤트 페이로드를 함수에 전달하기 전에 자동으로 C# 객체로 변환하도록 지시하는 어셈블리 특성입니다.namespace ExampleLambda: 네임스페이스를 정의합니다. C#에서 네임스페이스 이름은 파일 이름과 일치할 필요가 없습니다.public class Order {...}: 기대하는 입력 이벤트의 형태를 정의합니다.public class OrderHandler {...}: C# 클래스를 정의합니다. 그 안에 메인 핸들러 메서드와 기타 헬퍼 메서드를 정의합니다.private static readonly AmazonS3Client s3Client = new();: 메인 핸들러 메서드 밖에서 기본 자격 증명 공급자 체인으로 Amazon S3 클라이언트를 초기화합니다. 이로 인해 Lambda는 초기화 단계 동안 이 코드를 실행합니다.public async ... HandleRequest (Order order, ILambdaContext context): 메인 애플리케이션 로직을 담은 메인 핸들러 메서드입니다.private async Task UploadReceiptToS3(...) {}: 메인handleRequest핸들러 메서드가 참조하는 헬퍼 메서드입니다.
이 함수는 Amazon S3 SDK 클라이언트가 필요하므로 프로젝트의 의존성에 추가해야 합니다. src/ExampleCS로 이동해 다음 명령을 실행하면 됩니다.
dotnet add package AWSSDK.S3
기본적으로 생성된 aws-lambda-tools-defaults.json 파일에는 함수의 프로필이나 리전 정보가 들어 있지 않습니다. 또한 function-handler 문자열을 올바른 값(ExampleCS::ExampleLambda.OrderHandler::HandleRequest)으로 갱신해야 합니다. 특정 자격 증명 프로필과 리전을 사용하려면 이 갱신을 수동으로 하고 필요한 메타데이터를 추가할 수 있습니다. 예를 들어 aws-lambda-tools-defaults.json 파일은 다음과 비슷해야 합니다.
{
"Information": [
"This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
"To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
"dotnet lambda help",
"All the command line options for the Lambda command can be specified in this file."
],
"profile": "default",
"region": "us-east-1",
"configuration": "Release",
"function-architecture": "x86_64",
"function-runtime": "dotnet8",
"function-memory-size": 512,
"function-timeout": 30,
"function-handler": "ExampleCS::ExampleLambda.OrderHandler::HandleRequest"
}
이 함수가 올바르게 작동하려면 실행 역할(execution role)이 s3:PutObject 액션을 허용해야 합니다. 또한 RECEIPT_BUCKET 환경 변수를 정의했는지 확인하세요. 호출에 성공하면 Amazon S3 버킷에 영수증 파일이 들어 있어야 합니다.
클래스 라이브러리 핸들러
이 페이지의 메인 예제 코드는 클래스 라이브러리 핸들러를 보여줍니다. 클래스 라이브러리 핸들러는 다음과 같은 구조를 가집니다.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace NAMESPACE;
...
public class CLASSNAME {
public async Task<string> METHODNAME (...) {
...
}
}
Lambda 함수를 만들 때는 Handler 필드에 함수의 핸들러에 대한 정보를 문자열 형태로 Lambda에 제공해야 합니다. 이것은 Lambda가 함수가 호출될 때 코드의 어떤 메서드를 실행할지 알려줍니다. C#에서 클래스 라이브러리 핸들러의 핸들러 문자열 형식은 ASSEMBLY::TYPE::METHOD이며, 각각은 다음과 같습니다.
ASSEMBLY는 애플리케이션의 .NET 어셈블리 파일 이름입니다.Amazon.Lambda.ToolsCLI로 애플리케이션을 빌드하고.csproj파일의AssemblyName속성으로 어셈블리 이름을 설정하지 않았다면,ASSEMBLY는 단순히.csproj파일의 이름입니다.TYPE은 핸들러 타입의 전체 이름으로NAMESPACE.CLASSNAME입니다.METHOD는 코드에서 메인 핸들러 메서드의 이름인METHODNAME입니다.
이 페이지의 메인 예제 코드에서 어셈블리 이름이 ExampleCS라면 전체 핸들러 문자열은 ExampleCS::ExampleLambda.OrderHandler::HandleRequest입니다.
실행 가능 어셈블리 핸들러
C# Lambda 함수를 실행 가능 어셈블리로 정의할 수도 있습니다. 실행 가능 어셈블리 핸들러는 C#의 최상위 문(top-level statements) 기능을 사용하며, 컴파일러가 Main() 메서드를 생성하고 그 안에 함수 코드를 넣습니다. 실행 가능 어셈블리를 사용할 때는 Lambda 런타임을 부트스트랩해야 합니다.
이를 위해 코드에서 LambdaBootstrapBuilder.Create 메서드를 사용합니다. 이 메서드의 입력은 메인 핸들러 함수와 사용할 Lambda 직렬 변환기입니다. 다음은 C#의 실행 가능 어셈블리 핸들러 예제입니다.
namespace GetProductHandler;
IDatabaseRepository repo = new DatabaseRepository();
await LambdaBootstrapBuilder.Create<APIGatewayProxyRequest>(Handler, new DefaultLambdaJsonSerializer())
.Build()
.RunAsync();
async Task<APIGatewayProxyResponse> Handler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
var id = apigProxyEvent.PathParameters["id"];
var databaseRecord = await this.repo.GetById(id);
return new APIGatewayProxyResponse
{
StatusCode = (int)HttpStatusCode.OK,
Body = JsonSerializer.Serialize(databaseRecord)
};
};
실행 가능 어셈블리 핸들러의 Handler 필드에서 Lambda가 코드를 실행하는 방법을 알려주는 핸들러 문자열은 어셈블리의 이름입니다. 이 예제에서는 GetProductHandler입니다.
C# 함수의 유효한 핸들러 시그니처
C#에서 유효한 Lambda 핸들러 시그니처는 0~2개의 인자를 받습니다. 보통 핸들러 시그니처는 메인 예제처럼 두 개의 인자를 가집니다.
public async Task<string> HandleRequest(Order order, ILambdaContext context)
인자가 두 개일 때 첫 번째 인자는 이벤트 입력이어야 하고 두 번째 인자는 Lambda 컨텍스트 객체여야 합니다. 두 인자 모두 선택 사항입니다. 예를 들어 다음도 C#에서 유효한 Lambda 핸들러 시그니처입니다.
public async Task<string> HandleRequest()public async Task<string> HandleRequest(Order order)public async Task<string> HandleRequest(ILambdaContext context)
핸들러 시그니처의 기본 구문 외에도 몇 가지 추가 제한이 있습니다.
- 핸들러 시그니처에서
unsafe키워드를 사용할 수 없습니다. 다만 핸들러 메서드와 그 의존성 내부에서는unsafe컨텍스트를 사용할 수 있습니다. 자세한 내용은 Microsoft 문서 웹사이트의 'unsafe (C# reference)'를 참고하세요. - 핸들러는
params키워드를 사용하거나ArgIterator를 입력·반환 파라미터로 사용할 수 없습니다. 이 키워드들은 가변 개수 파라미터를 지원합니다. 핸들러가 받을 수 있는 인자의 최대 개수는 2개입니다. - 핸들러는 제네릭 메서드일 수 없습니다. 즉
<T>같은 제네릭 타입 파라미터를 사용할 수 없습니다. - Lambda는 시그니처에
async void가 있는 async 핸들러를 지원하지 않습니다.
핸들러 명명 규칙
C#의 Lambda 핸들러에는 엄격한 명명 제한이 없습니다. 다만 함수를 배포할 때 Lambda에 올바른 핸들러 문자열을 제공해야 합니다. 올바른 핸들러 문자열은 클래스 라이브러리 핸들러를 배포하는지 실행 가능 어셈블리 핸들러를 배포하는지에 따라 달라집니다.
핸들러에 어떤 이름이든 사용할 수 있지만, C#의 함수 이름은 일반적으로 PascalCase입니다. 또한 파일 이름이 클래스 이름이나 핸들러 이름과 일치할 필요는 없지만, 클래스 이름이 OrderHandler라면 OrderHandler.cs 같은 파일 이름을 사용하는 것이 일반적으로 모범 사례입니다. 예를 들어 이 예제의 파일 이름을 Function.cs에서 OrderHandler.cs로 바꿀 수 있습니다.
C# Lambda 함수에서의 직렬화
JSON은 Lambda 함수에서 가장 흔하고 표준적인 입력 형식입니다. 이 예제에서 함수는 다음과 같은 입력을 기대합니다.
{
"orderId": "12345",
"amount": 199.99,
"item": "Wireless Headphones"
}
C#에서는 클래스로 기대하는 입력 이벤트의 형태를 정의할 수 있습니다. 이 예제에서는 이 입력을 모델링하는 Order 클래스를 정의합니다.
public class Order
{
public string OrderId { get; set; } = string.Empty;
public double Amount { get; set; }
public string Item { get; set; } = string.Empty;
}
Lambda 함수가 Stream 객체가 아닌 다른 입력·출력 타입을 사용한다면 애플리케이션에 직렬화 라이브러리를 추가해야 합니다. 그런 다음 JSON 입력을 정의한 클래스의 인스턴스로 변환할 수 있습니다. Lambda의 C# 함수에는 리플렉션 기반 직렬화와 소스 생성 직렬화 두 가지 직렬화 방법이 있습니다.
리플렉션 기반 직렬화
AWS는 애플리케이션에 빠르게 추가할 수 있는 미리 빌드된 라이브러리를 제공합니다. 이 라이브러리들은 리플렉션을 사용해 직렬화를 구현합니다. 리플렉션 기반 직렬화를 구현하려면 다음 패키지 중 하나를 사용하세요.
Amazon.Lambda.Serialization.SystemTextJson– 내부적으로System.Text.Json을 사용해 직렬화 작업을 수행합니다.Amazon.Lambda.Serialization.Json– 내부적으로Newtonsoft.Json을 사용해 직렬화 작업을 수행합니다.
Amazon.Lambda.Core 라이브러리의 일부로 제공되는 ILambdaSerializer 인터페이스를 구현해 자체 직렬화 라이브러리를 만들 수도 있습니다. 이 인터페이스는 두 가지 메서드를 정의합니다.
T Deserialize<T>(Stream requestStream);– 이 메서드를 구현해 Invoke API의 요청 페이로드를 Lambda 함수 핸들러에 전달되는 객체로 역직렬화합니다.T Serialize<T>(T response, Stream responseStream);– 이 메서드를 구현해 Lambda 함수 핸들러가 반환한 결과를 Invoke API 작업이 반환하는 응답 페이로드로 직렬화합니다.
이 페이지의 메인 예제는 리플렉션 기반 직렬화를 사용합니다. 리플렉션 기반 직렬화는 AWS Lambda에서 별다른 설정 없이 즉시 작동해 단순함을 원한다면 좋은 선택입니다. 다만 함수 메모리 사용량이 더 필요합니다. 또한 런타임 리플렉션 때문에 함수 지연 시간이 더 높을 수 있습니다.
소스 생성 직렬화
소스 생성 직렬화를 사용하면 컴파일 타임에 직렬화 코드가 생성됩니다. 이는 리플렉션의 필요를 없애고 함수 성능을 개선할 수 있습니다. 함수에서 소스 생성 직렬화를 사용하려면 다음을 수행해야 합니다.
JsonSerializerContext에서 상속하는 새 partial 클래스를 만들고, 직렬화·역직렬화가 필요한 모든 타입에JsonSerializable특성을 추가합니다.LambdaSerializer가SourceGeneratorLambdaJsonSerializer<T>를 사용하도록 구성합니다.- 애플리케이션 코드의 수동 직렬화·역직렬화를 새로 만든 클래스를 사용하도록 갱신합니다.
다음 예제는 이 페이지의 리플렉션 기반 직렬화를 사용하는 메인 예제를 소스 생성 직렬화를 사용하도록 수정하는 방법을 보여줍니다.
using System.Text.Json;
using System.Text.Json.Serialization;
...
public class Order
{
public string OrderId { get; set; } = string.Empty;
public double Amount { get; set; }
public string Item { get; set; } = string.Empty;
}
[JsonSerializable(typeof(Order))]
public partial class OrderJsonContext : JsonSerializerContext {}
public class OrderHandler
{
...
public async Task<string> HandleRequest(string input, ILambdaContext context)
{
var order = JsonSerializer.Deserialize(input, OrderJsonContext.Default.Order);
...
}
}
소스 생성 직렬화는 리플렉션 기반 직렬화보다 설정이 더 필요합니다. 다만 소스 생성 직렬화를 사용하는 함수는 컴파일 타임 코드 생성 덕분에 메모리를 덜 쓰고 성능이 더 좋은 경향이 있습니다. 함수 콜드 스타트를 없애는 데 도움이 되도록 소스 생성 직렬화로 전환하는 것을 고려하세요.
Lambda에서 네이티브 AOT(ahead-of-time) 컴파일을 사용하려면 소스 생성 직렬화를 사용해야 합니다.
파일 기반 함수
.NET 10에서 소개된 파일 기반 앱(file-based apps)을 사용하면 .csproj 파일이나 디렉터리 구조 없이 단일 .cs 파일로 .NET 애플리케이션을 빌드할 수 있습니다. Lambda는 .NET 10부터 파일 기반 함수를 지원합니다. 이들은 C#에서 Lambda 함수를 빌드하는 간소화되고 가벼운 방법을 제공합니다.
C# 파일 기반 Lambda 함수를 만들기 시작하는 가장 빠른 방법은 Amazon.Lambda.Templates 패키지를 사용하는 것입니다. 패키지를 설치하려면 다음 명령을 실행하세요.
dotnet new install Amazon.Lambda.Templates
다음으로 C# 파일 기반 Lambda 예제 함수를 만듭니다.
dotnet new lambda.FileBased -n MyLambdaFunction
파일 기반 함수는 실행 가능 어셈블리 핸들러를 사용합니다. 따라서 Amazon.Lambda.RuntimeSupport NuGet 패키지를 포함하고 LambdaBootstrapBuilder.Create 메서드를 사용해 이벤트 타입에 대한 .NET 핸들러 함수를 등록하고 .NET Lambda 런타임 클라이언트를 시작해야 합니다.
파일 기반 함수는 기본적으로 .NET Native AOT를 사용하며, 이는 소스 생성 직렬화를 요구합니다. 소스 파일에서 #:property PublishAot=false를 지정해 Native AOT를 비활성화할 수 있습니다. Lambda에서 Native AOT 사용에 대한 자세한 내용은 '.NET Lambda 함수 코드를 네이티브 런타임 형식으로 컴파일' 문서를 참고하세요.
Lambda 컨텍스트 객체 접근 및 사용
Lambda 컨텍스트 객체는 호출, 함수, 실행 환경에 대한 정보를 담고 있습니다. 이 예제에서 컨텍스트 객체는 Amazon.Lambda.Core.ILambdaContext 타입이며 메인 핸들러 함수의 두 번째 인자입니다.
public async Task<string> HandleRequest(Order order, ILambdaContext context) {
...
}
컨텍스트 객체는 선택적 입력입니다. 유효한 핸들러 시그니처에 대한 자세한 내용은 'C# 함수의 유효한 핸들러 시그니처'를 참고하세요.
컨텍스트 객체는 Amazon CloudWatch에 함수 로그를 만드는 데 유용합니다. 로깅을 위해 context.getLogger() 메서드를 사용해 LambdaLogger 객체를 얻을 수 있습니다. 이 예제에서는 처리에 어떤 이유로든 실패하면 로거를 사용해 오류 메시지를 기록할 수 있습니다.
context.Logger.LogError($"Failed to process order: {ex.Message}");
로깅 외에도 함수 모니터링에 컨텍스트 객체를 사용할 수 있습니다. 컨텍스트 객체에 대한 자세한 내용은 'Lambda 컨텍스트 객체로 C# 함수 정보 검색' 문서를 참고하세요.
핸들러에서 SDK for .NET v3 사용
Lambda 함수로 다른 AWS 리소스와 상호작용하거나 갱신하는 일이 많습니다. 이런 리소스와 연결하는 가장 간단한 방법은 SDK for .NET v3를 사용하는 것입니다.
SDK for .NET (v2)은 더 이상 사용되지 않습니다(deprecated). 앞으로는 SDK for .NET v3만 사용할 것을 권장합니다.
다음 Amazon.Lambda.Tools 명령으로 프로젝트에 SDK 의존성을 추가할 수 있습니다.
dotnet add package <package_name>
예를 들어 이 페이지의 메인 예제에서는 영수증을 S3에 업로드하기 위해 Amazon S3 API를 사용해야 합니다. 다음 명령으로 Amazon S3 SDK 클라이언트를 import 할 수 있습니다.
dotnet add package AWSSDK.S3
이 명령은 프로젝트에 의존성을 추가합니다. 또한 프로젝트의 .csproj 파일에 다음과 비슷한 줄이 있어야 합니다.
<PackageReference Include="AWSSDK.S3" Version="3.7.2.18" />
그런 다음 C# 코드에서 의존성을 직접 import 합니다.
using Amazon.S3;
using Amazon.S3.Model;
예제 코드는 (기본 자격 증명 공급자 체인을 사용해) Amazon S3 클라이언트를 다음과 같이 초기화합니다.
private static readonly AmazonS3Client s3Client = new();
이 예제에서는 함수를 호출할 때마다 초기화하지 않도록 메인 핸들러 함수 밖에서 Amazon S3 클라이언트를 초기화했습니다. SDK 클라이언트를 초기화한 뒤에는 이를 사용해 다른 AWS 서비스와 상호작용할 수 있습니다. 예제 코드는 Amazon S3 PutObject API를 다음과 같이 호출합니다.
var putRequest = new PutObjectRequest
{
BucketName = bucketName,
Key = key,
ContentBody = receiptContent,
ContentType = "text/plain"
};
await s3Client.PutObjectAsync(putRequest);
환경 변수 접근
핸들러 코드에서 System.Environment.GetEnvironmentVariable 메서드로 어떤 환경 변수든 참조할 수 있습니다. 이 예제에서는 다음 코드 줄로 정의된 RECEIPT_BUCKET 환경 변수를 참조합니다.
string? bucketName = Environment.GetEnvironmentVariable("RECEIPT_BUCKET");
if (string.IsNullOrWhiteSpace(bucketName))
{
throw new ArgumentException("RECEIPT_BUCKET environment variable is not set");
}
전역 상태(Global state) 사용
Lambda는 함수를 처음 호출하기 전의 초기화 단계에서 정적 코드와 클래스 생성자를 실행합니다. 초기화 동안 만들어진 리소스는 호출 사이에 메모리에 남으므로, 함수를 호출할 때마다 리소스를 다시 만들 필요가 없습니다.
예제 코드에서 S3 클라이언트 초기화 코드는 메인 핸들러 메서드 밖에 있습니다. 런타임은 함수가 첫 이벤트를 처리하기 전에 클라이언트를 초기화하며, 이로 인해 처리 시간이 더 길어질 수 있습니다. 이후 이벤트는 Lambda가 클라이언트를 다시 초기화할 필요가 없어 훨씬 빠릅니다.
Lambda Annotations 프레임워크로 함수 코드 단순화
Lambda Annotations는 C#으로 Lambda 함수를 작성하는 것을 단순화하는 .NET 8용 프레임워크입니다. Annotations 프레임워크는 소스 생성기(source generators)를 사용해 Lambda 프로그래밍 모델에서 단순화된 코드로 변환하는 코드를 생성합니다. Annotations 프레임워크를 사용하면 일반 프로그래밍 모델로 작성한 Lambda 함수의 코드 상당 부분을 대체할 수 있습니다. 이 프레임워크로 작성한 코드는 더 단순한 표현식을 사용해 비즈니스 로직에 집중할 수 있게 합니다. 예제는 nuget 문서의 Amazon.Lambda.Annotations를 참고하세요.
Lambda Annotations를 사용하는 전체 애플리케이션 예제는 awsdocs/aws-doc-sdk-examples GitHub 저장소의 PhotoAssetManager 예제를 참고하세요. PamApiAnnotations 디렉터리의 메인 Function.cs 파일은 Lambda Annotations를 사용합니다. 비교를 위해 PamApi 디렉터리에는 일반 Lambda 프로그래밍 모델로 작성된 동등한 파일이 있습니다.
Lambda Annotations 프레임워크와 의존성 주입
Lambda Annotations 프레임워크를 사용해 익숙한 구문으로 Lambda 함수에 의존성 주입을 추가할 수도 있습니다. Startup.cs 파일에 [LambdaStartup] 특성을 추가하면 Lambda Annotations 프레임워크가 컴파일 타임에 필요한 코드를 생성합니다.
[LambdaStartup]
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDatabaseRepository, DatabaseRepository>();
}
}
Lambda 함수는 생성자 주입을 사용하거나 [FromServices] 특성으로 개별 메서드에 주입해 서비스를 주입할 수 있습니다.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace GetProductHandler;
public class Function
{
private readonly IDatabaseRepository _repo;
public Function(IDatabaseRepository repo)
{
this._repo = repo;
}
[LambdaFunction]
[HttpApi(LambdaHttpMethod.Get, "/product/{id}")]
public async Task<Product> FunctionHandler([FromServices] IDatabaseRepository repository, string id)
{
return await this._repo.GetById(id);
}
}
C# Lambda 함수의 코드 모범 사례
Lambda 함수를 만들 때 다음 목록의 지침을 지켜 모범 코딩 사례를 따르세요.
- Lambda 핸들러를 핵심 로직에서 분리하세요. 이렇게 하면 더 단위 테스트하기 쉬운 함수를 만들 수 있습니다.
- 함수 배포 패키지의 의존성을 제어하세요. AWS Lambda 실행 환경에는 많은 라이브러리가 포함되어 있습니다. 최신 기능 집합과 보안 업데이트를 활성화하기 위해 Lambda는 이러한 라이브러리를 주기적으로 업데이트합니다. 이 업데이트는 Lambda 함수 동작에 미묘한 변화를 가져올 수 있습니다. 함수가 사용하는 의존성을 완전히 제어하려면 모든 의존성을 배포 패키지와 함께 패키징하세요.
- 의존성의 복잡성을 최소화하세요. 실행 환경 시작 시 빠르게 로드되는 더 단순한 프레임워크를 선호합니다.
- 배포 패키지 크기를 런타임에 필요한 만큼만 유지하세요. 이렇게 하면 호출 전에 배포 패키지를 다운로드·압축 해제하는 데 걸리는 시간이 줄어듭니다. .NET으로 작성한 함수는 배포 패키지의 일부로 전체 AWS SDK 라이브러리를 업로드하지 마세요. 대신 필요한 SDK 구성 요소를 골라주는 모듈(예: DynamoDB, Amazon S3 SDK 모듈, Lambda 핵심 라이브러리)에 선택적으로 의존하세요.
- 실행 환경 재사용을 활용해 함수 성능을 개선하세요. SDK 클라이언트와 데이터베이스 연결을 함수 핸들러 밖에서 초기화하고, 정적 자산을 로컬
/tmp디렉터리에 캐시하세요. 같은 함수 인스턴스가 처리하는 이후 호출은 이 리소스를 재사용할 수 있습니다. 함수 실행 시간을 줄여 비용을 절약합니다. - 호출 간 데이터 누출을 막으려면 실행 환경에 사용자 데이터, 이벤트, 보안상 의미가 있는 정보를 저장하지 마세요. 핸들러 내 메모리에 저장할 수 없는 변경 가능한 상태에 의존한다면 사용자별로 별도의 함수나 별도 버전의 함수를 만드는 것을 고려하세요.
- keep-alive 지시문을 사용해 지속 연결을 유지하세요. Lambda는 시간이 지나면 유휴 연결을 정리합니다. 함수 호출 시 유휴 연결을 재사용하려 하면 연결 오류가 발생합니다. 지속 연결을 유지하려면 런타임과 연결된 keep-alive 지시문을 사용하세요. 예시는 'Node.js에서 Keep-Alive로 연결 재사용'을 참고하세요.
- 환경 변수로 작동 파라미터를 함수에 전달하세요. 예를 들어 Amazon S3 버킷에 쓸 때 버킷 이름을 하드코딩하지 말고 환경 변수로 구성하세요.
- Lambda 함수에서 재귀 호출을 피하세요. 함수가 자기 자신을 호출하거나 함수를 다시 호출할 프로세스를 시작하면 의도치 않은 호출량과 비용 증가가 발생할 수 있습니다. 의도치 않은 호출량이 보이면, 코드를 갱신하는 동안 즉시 함수 예약 동시성을 0으로 설정해 모든 호출을 제한하세요.
- Lambda 함수 코드에서 문서화되지 않은 비공개 API를 사용하지 마세요. Lambda 관리형 런타임의 경우 Lambda는 Lambda 내부 API에 보안·기능 업데이트를 주기적으로 적용합니다. 이런 내부 API 업데이트는 하위 호환성이 깨질 수 있으며, 이러한 비공개 API에 의존하는 함수는 호출 실패 같은 의도치 않은 결과를 겪을 수 있습니다. 공개적으로 사용 가능한 API 목록은 API 레퍼런스를 참고하세요.
- 멱등(idempotent) 코드를 작성하세요. 함수에 멱등 코드를 작성하면 중복 이벤트가 동일하게 처리됩니다. 코드는 이벤트를 올바르게 검증하고 중복 이벤트를 우아하게 처리해야 합니다. 자세한 내용은 '내 Lambda 함수를 멱등하게 만드는 방법'을 참고하세요.