AWS에서 Lambda와 Java 25, API Gateway 및 DynamoDB를 사용한 서버리스 애플리케이션 - 파트 1 샘플 애플리케이션
Source: Dev.to
이 시리즈 기사에서는 새로 출시된 Java 25 런타임을 사용한 Lambda로 AWS에서 서버리스 애플리케이션을 구현하는 방법을 설명합니다.
또한 API Gateway, DynamoDB, 그리고 인프라스트럭처‑코드(IaC) 정의를 위한 AWS SAM을 사용할 것입니다.
시리즈는 세 부분으로 나뉩니다:
- Baseline – 애플리케이션을 배포하고 최적화 없이 콜드/웜 시작 시간을 측정합니다.
- Cold‑start reduction – Lambda SnapStart, 프라이밍, GraalVM Native Image와 같은 기술을 탐구합니다.
- Extended example – DynamoDB를 관계형 서버리스 Amazon Aurora DSQL 데이터베이스로 교체하고 Hibernate ORM을 사용하는 부가 예제 시리즈입니다.
샘플 코드는 GitHub에서 확인할 수 있습니다: aws‑lambda‑java‑25‑dynamodb.
아키텍처 개요
샘플 애플리케이션은 간단한 제품 카탈로그를 제공합니다:
- Create 제품 생성 (POST)
- Retrieve 제품을 ID로 조회 (GET)
It uses the following AWS services:
| 서비스 | 역할 |
|---|---|
| Amazon API Gateway | Lambda 함수용 REST API를 노출합니다 |
| AWS Lambda | Java 25 코드(또는 GraalVM 네이티브 이미지)를 실행합니다 |
| Amazon DynamoDB | 제품 데이터용 NoSQL 영속성 레이어 |
| AWS SAM | 전체 스택을 위한 선언형 IaC |
Assumption: 위 서비스와 AWS 서버리스 아키텍처에 대한 기본적인 이해가 있다고 가정합니다.
전제 조건
| 도구 | 버전 |
|---|---|
| Java | 25 |
| Maven | 최신 |
| AWS CLI | 최신 |
| SAM CLI | 최신 |
| GraalVM (선택 사항, 네이티브 이미지용) | native-image 컴포넌트 포함 최신 |
우선 Java 25 버전을 빌드하고 배포한 뒤, 이후에 GraalVM 네이티브 이미지를 컴파일하고 맞춤 런타임으로 실행합니다.
Source:
AWS SAM 템플릿 – IaC 하이라이트
아래는 template.yaml의 관련 부분입니다. Lambda 함수에 영향을 주는 섹션만 표시했습니다.
전역 함수 설정
Globals:
Function:
CodeUri: .
Runtime: java25
# SnapStart (uncomment to enable)
# SnapStart:
# ApplyOn: PublishedVersions
Timeout: 30
MemorySize: 1024
Architectures:
- x86_64
Environment:
Variables:
REGION: !Sub ${AWS::Region}
PRODUCT_TABLE_NAME: !Ref ProductsTable
# … other variables …GetProductById 함수
GetProductByIdFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: GetProductByIdJava25WithDynamoDB
AutoPublishAlias: liveVersion
Handler: software.amazonaws.example.product.handler.GetProductByIdHandler::handleRequest
Policies:
- DynamoDBReadPolicy:
TableName: !Ref ProductsTable
Events:
GetRequestById:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /products/{id}
Method: getPostProduct 함수
PostProductJava25WithDynamoDB 정의는 동일한 패턴을 따릅니다 (간략히 생략).
Lambda 핸들러 – GetProductByIdHandler
@Override
public APIGatewayProxyResponseEvent handleRequest(
APIGatewayProxyRequestEvent requestEvent,
Context context) throws JsonProcessingException {
var id = requestEvent.getPathParameters().get("id");
var optionalProduct = productDao.getProduct(id);
if (optionalProduct.isEmpty()) {
return new APIGatewayProxyResponseEvent()
.withStatusCode(HttpStatusCode.NOT_FOUND)
.withBody("Product with id = " + id + " not found");
}
return new APIGatewayProxyResponseEvent()
.withStatusCode(HttpStatusCode.OK)
.withBody(objectMapper.writeValueAsString(optionalProduct.get()));
}- 이 메서드는
APIGatewayProxyRequestEvent를 받습니다 (API Gateway에 의해 호출됨). id경로 파라미터를 추출하고,productDao.getProduct(id)를 통해 DynamoDB를 조회한 뒤, 404 또는 200 응답을 JSON 페이로드와 함께 반환합니다.
CreateProductHandler(POST에 사용)는 동일한 구조를 따릅니다.
도메인 모델 – 제품 엔터티
public record Product(String id, String name, BigDecimal price) {}제품을 나타내는 간단한 불변 레코드입니다.
영속성 레이어 – ProductDao (발췌)
public class ProductDao {
private final DynamoDbClient dynamoDb;
private final String tableName;
private final ObjectMapper objectMapper = new ObjectMapper();
public ProductDao(DynamoDbClient dynamoDb, String tableName) {
this.dynamoDb = dynamoDb;
this.tableName = tableName;
}
public Optional getProduct(String id) {
GetItemResponse response = dynamoDb.getItem(
GetItemRequest.builder()
.tableName(tableName)
.key(Map.of("id", AttributeValue.builder().s(id).build()))
.build());
if (response.hasItem()) {
var item = response.item();
var product = objectMapper.convertValue(item, Product.class);
return Optional.of(product);
}
return Optional.empty();
}
// … methods for putItem, deleteItem, etc. …
}- AWS SDK for Java 2.x를 사용하여 DynamoDB와 상호작용합니다.
getProduct는GetItemRequest를 생성하고 실행한 뒤, 결과를Product레코드로 매핑합니다.
다음 단계
- 배포 SAM 스택 (
sam build && sam deploy). - 호출 API 엔드포인트를 실행하고 콜드/웜 시작 시간을 기록합니다.
- SnapStart 활성화 (주석 처리된
SnapStart블록을 해제)하고 다시 측정합니다. - GraalVM 네이티브 이미지 빌드하고 맞춤 런타임으로 배포하여 콜드 스타트 감소를 추가로 진행합니다.
다음 기사에서는 각 최적화 기술을 더 깊이 파고들 예정이니 기대해 주세요!
DynamoDB에서 제품 조회
GetItemResponse getItemResponse = dynamoDbClient.getItem(
GetItemRequest.builder()
.key(Map.of("PK", AttributeValue.builder().s(id).build()))
.tableName(PRODUCT_TABLE_NAME)
.build());
if (getItemResponse.hasItem()) {
return Optional.of(ProductMapper.productFromDynamoDB(getItemResponse.item()));
} else {
return Optional.empty();
}여기서는 DynamoDbClient 인스턴스를 사용하여 DynamoDB 테이블을 조회하는 GetItemRequest를 생성합니다.
테이블 이름은 System.getenv("PRODUCT_TABLE_NAME")를 통해 환경 변수( AWS SAM 템플릿에서 설정)에서 가져옵니다.
제품이 존재하면, 사용자 정의 ProductMapper가 DynamoDB 항목을 Product 엔티티로 변환합니다.
빌드 및 배포
# Build the application
mvn clean package
# Deploy with SAM (guided mode)
sam deploy -g배포가 완료되면 SAM은 맞춤형 Amazon API Gateway URL을 반환합니다. 이 URL은 제품을 생성하고 조회하는 데 사용됩니다. API는 API 키로 보호되며, X-API-Key 헤더에 포함하여 전송해야 합니다(template.yaml에 정의된 MyApiKey를 참조).
API 키: a6ZbcDefQW12BN56WEVDDB25
예시 요청
제품 생성 (ID = 1)
curl -X PUT \
-d '{ "id": 1, "name": "Print 10x13", "price": 0.15 }' \
-H "X-API-Key: a6ZbcDefQW12BN56WEVDDB25" \
https://{API_GATEWAY_URL}/prod/products제품 조회 (ID = 1)
curl -H "X-API-Key: a6ZbcDefQW12BN56WEVDDB25" \
https://{API_GATEWAY_URL}/prod/products/1({API_GATEWAY_URL}를 sam deploy가 반환한 URL로 교체하십시오.)
다음은?
이 글에서는 샘플 애플리케이션을 소개했습니다. 다음 글에서는 최적화 없이 Lambda 함수의 콜드‑ 및 웜‑시작 시간을 측정합니다.
추후에는 관계형 서버리스 Amazon Aurora (PostgreSQL) 데이터베이스와 Hibernate ORM을 사용해 동일한 Lambda 성능 측정을 수행할 예정입니다.
내용이 마음에 드신다면:
- ⭐ GitHub에서 제 저장소에 Star를 달아 주세요
- 더 많은 기술 글과 앞으로 있을 공개 강연 소식을 받으려면 팔로우 해 주세요
- 추가 자료는 제 개인 웹사이트를 방문해 주세요
감사합니다!