프로젝트 1: MyTodoList - 모바일 애플리케이션
요구사항
기능:
- 사용자 인증 (로그인/회원가입)
- Todo 항목 CRUD
- 실시간 동기화
- 오프라인 지원
제약:
- 서버 관리 없이
- 자동 확장
- 저렴한 비용
아키텍처
[모바일 앱]
↓ REST API
[Amazon Cognito] ← 인증
↓ 인증 토큰
[API Gateway]
↓
[Lambda Functions]
├─ Create Todo
├─ Read Todos
├─ Update Todo
└─ Delete Todo
↓
[DynamoDB]
├─ Users Table
└─ Todos Table
1단계: Cognito 사용자 풀 생성
# 사용자 풀 생성
aws cognito-idp create-user-pool \
--pool-name MyTodoList \
--policies '{
"PasswordPolicy": {
"MinimumLength": 8,
"RequireUppercase": true,
"RequireLowercase": true,
"RequireNumbers": true
}
}' \
--auto-verified-attributes email
# 앱 클라이언트 생성
aws cognito-idp create-user-pool-client \
--user-pool-id us-east-1_xxxxxxxxx \
--client-name TodoApp \
--no-generate-secret
2단계: DynamoDB 테이블 설계
# Todos 테이블 생성
aws dynamodb create-table \
--table-name Todos \
--attribute-definitions \
AttributeName=userId,AttributeType=S \
AttributeName=todoId,AttributeType=S \
--key-schema \
AttributeName=userId,KeyType=HASH \
AttributeName=todoId,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST
테이블 구조:
Todos 테이블:
- userId (Partition Key): user123
- todoId (Sort Key): todo_20241119_001
- title: "Buy groceries"
- completed: false
- createdAt: 1700000000
3단계: Lambda 함수 구현
import json
import boto3
import uuid
from datetime import datetime
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Todos')
def lambda_handler(event, context):
# Cognito에서 userId 가져오기
user_id = event['requestContext']['authorizer']['claims']['sub']
http_method = event['httpMethod']
# Create Todo
if http_method == 'POST':
body = json.loads(event['body'])
todo_id = f"todo_{int(datetime.now().timestamp())}"
table.put_item(Item={
'userId': user_id,
'todoId': todo_id,
'title': body['title'],
'completed': False,
'createdAt': int(datetime.now().timestamp())
})
return {
'statusCode': 200,
'headers': {'Access-Control-Allow-Origin': '*'},
'body': json.dumps({'todoId': todo_id})
}
# Get Todos
elif http_method == 'GET':
response = table.query(
KeyConditionExpression='userId = :uid',
ExpressionAttributeValues={':uid': user_id}
)
return {
'statusCode': 200,
'headers': {'Access-Control-Allow-Origin': '*'},
'body': json.dumps(response['Items'])
}
# Update Todo
elif http_method == 'PUT':
todo_id = event['pathParameters']['id']
body = json.loads(event['body'])
table.update_item(
Key={'userId': user_id, 'todoId': todo_id},
UpdateExpression='SET completed = :c',
ExpressionAttributeValues={':c': body['completed']}
)
return {
'statusCode': 200,
'headers': {'Access-Control-Allow-Origin': '*'},
'body': json.dumps({'message': 'Updated'})
}
# Delete Todo
elif http_method == 'DELETE':
todo_id = event['pathParameters']['id']
table.delete_item(
Key={'userId': user_id, 'todoId': todo_id}
)
return {
'statusCode': 200,
'headers': {'Access-Control-Allow-Origin': '*'},
'body': json.dumps({'message': 'Deleted'})
}
4단계: API Gateway 설정
# REST API 생성
aws apigateway create-rest-api \
--name TodoAPI \
--endpoint-configuration types=REGIONAL
# Cognito Authorizer 추가
aws apigateway create-authorizer \
--rest-api-id api-id \
--name CognitoAuth \
--type COGNITO_USER_POOLS \
--provider-arns arn:aws:cognito-idp:region:account:userpool/pool-id \
--identity-source method.request.header.Authorization
# 리소스 및 메서드 생성
# GET /todos
# POST /todos
# PUT /todos/{id}
# DELETE /todos/{id}
5단계: 모바일 앱 통합
// React Native 예시
import { Auth, API } from 'aws-amplify';
// 로그인
async function signIn(email, password) {
const user = await Auth.signIn(email, password);
return user;
}
// Todo 가져오기
async function getTodos() {
const todos = await API.get('TodoAPI', '/todos', {
headers: {
Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}`
}
});
return todos;
}
// Todo 추가
async function createTodo(title) {
await API.post('TodoAPI', '/todos', {
body: { title },
headers: {
Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}`
}
});
}
향상: 실시간 동기화
[DynamoDB Streams]
↓ 변경 캡처
[Lambda: Broadcast]
↓
[API Gateway WebSocket]
↓
[모바일 앱들] ← 실시간 업데이트
프로젝트 2: MyBlog.com - 서버리스 웹사이트
요구사항
기능:
- 정적 웹사이트 호스팅
- 동적 콘텐츠 (블로그 글)
- 이미지 업로드 및 리사이징
- 검색 기능
- 댓글 시스템
제약:
- 높은 트래픽 대응
- 글로벌 배포
- SEO 최적화
아키텍처
[CloudFront] ← 글로벌 CDN
├─ /static/* → [S3: Static Assets]
│ └─ HTML, CSS, JS
└─ /api/* → [API Gateway]
↓
[Lambda Functions]
↓
[DynamoDB]
상세 구성
프론트엔드 (React/Next.js)
[S3 Bucket: myblog-frontend]
├─ index.html
├─ _next/static/
├─ images/
└─ favicon.ico
# 빌드 및 배포
npm run build
aws s3 sync ./out s3://myblog-frontend/
aws cloudfront create-invalidation \
--distribution-id dist-id \
--paths "/*"
백엔드 API
# Lambda: Get Posts
def get_posts(event, context):
table = boto3.resource('dynamodb').Table('BlogPosts')
response = table.scan(
FilterExpression='published = :true',
ExpressionAttributeValues={':true': True},
ScanIndexForward=False
)
posts = response['Items']
return {
'statusCode': 200,
'headers': {'Access-Control-Allow-Origin': '*'},
'body': json.dumps(posts)
}
# Lambda: Create Post
def create_post(event, context):
body = json.loads(event['body'])
post_id = str(uuid.uuid4())
table = boto3.resource('dynamodb').Table('BlogPosts')
table.put_item(Item={
'postId': post_id,
'title': body['title'],
'content': body['content'],
'author': body['author'],
'published': False,
'createdAt': int(datetime.now().timestamp())
})
return {
'statusCode': 201,
'headers': {'Access-Control-Allow-Origin': '*'},
'body': json.dumps({'postId': post_id})
}
이미지 처리 파이프라인
[S3: uploads/] (원본 업로드)
↓ S3 Event
[Lambda: Image Processing]
├─ 썸네일 생성 (300x300)
├─ 중간 크기 (800x600)
└─ 워터마크 추가
↓
[S3: processed/]
├─ thumbnails/
├─ medium/
└─ full/
from PIL import Image
import boto3
from io import BytesIO
s3 = boto3.client('s3')
def lambda_handler(event, context):
# S3 이벤트 파싱
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
# 원본 이미지 다운로드
obj = s3.get_object(Bucket=bucket, Key=key)
img = Image.open(BytesIO(obj['Body'].read()))
# 썸네일 생성
thumb = img.copy()
thumb.thumbnail((300, 300))
# S3 업로드
buffer = BytesIO()
thumb.save(buffer, 'JPEG')
buffer.seek(0)
s3.put_object(
Bucket=bucket,
Key=key.replace('uploads/', 'thumbnails/'),
Body=buffer,
ContentType='image/jpeg'
)
검색 기능 (OpenSearch)
[DynamoDB Streams]
↓
[Lambda: Index to OpenSearch]
↓
[OpenSearch]
검색 요청:
[API Gateway] → [Lambda: Search] → [OpenSearch] → 결과 반환
프로젝트 3: 마이크로서비스 아키텍처
이커머스 플랫폼
[API Gateway]
├─ /users → [User Service]
│ ├─ Lambda: Auth
│ ├─ Lambda: Profile
│ └─ DynamoDB: Users
│
├─ /products → [Product Service]
│ ├─ Lambda: List
│ ├─ Lambda: Detail
│ └─ DynamoDB: Products
│
├─ /orders → [Order Service]
│ ├─ Lambda: Create Order
│ ├─ Lambda: Get Orders
│ ├─ DynamoDB: Orders
│ └─ Step Functions: Order Workflow
│
└─ /payments → [Payment Service]
├─ Lambda: Process
└─ DynamoDB: Transactions
주문 처리 워크플로우 (Step Functions)
{
"Comment": "Order Processing Workflow",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:validate-order",
"Next": "CheckInventory"
},
"CheckInventory": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:check-inventory",
"Next": "IsInStock"
},
"IsInStock": {
"Type": "Choice",
"Choices": [{
"Variable": "$.inStock",
"BooleanEquals": true,
"Next": "ProcessPayment"
}],
"Default": "OutOfStock"
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:process-payment",
"Next": "IsPaymentSuccess"
},
"IsPaymentSuccess": {
"Type": "Choice",
"Choices": [{
"Variable": "$.paymentStatus",
"StringEquals": "SUCCESS",
"Next": "CreateShipment"
}],
"Default": "PaymentFailed"
},
"CreateShipment": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:create-shipment",
"Next": "SendConfirmation"
},
"SendConfirmation": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:send-email",
"End": true
},
"OutOfStock": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:notify-out-of-stock",
"End": true
},
"PaymentFailed": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:function:refund",
"End": true
}
}
}
이벤트 기반 통신
[Order Service]
↓ 주문 생성
[EventBridge]
├─ Rule: Order Created
│ ├─ Target 1: [Lambda: Update Inventory]
│ ├─ Target 2: [Lambda: Send Email]
│ └─ Target 3: [Lambda: Analytics]
│
└─ Rule: Order Cancelled
├─ Target 1: [Lambda: Restore Inventory]
└─ Target 2: [Lambda: Refund]
소프트웨어 업데이트 배포
요구사항
상황:
- 소프트웨어 회사
- 전 세계 고객에게 업데이트 배포
- 대용량 파일 (수GB)
문제:
- 동시 다운로드 폭주
- 높은 대역폭 비용
- 느린 다운로드 속도
솔루션 아키텍처
[S3: Software Releases]
↓ Origin
[CloudFront] ← 글로벌 캐싱
↓
[사용자들]
추가:
[Lambda@Edge: Version Check]
├─ 사용자 버전 확인
├─ 최신 버전 리다이렉트
└─ 다운로드 통계 수집
단계별 배포 (Canary Release)
# Lambda@Edge: Canary Deployment
def lambda_handler(event, context):
request = event['Records'][0]['cf']['request']
# 사용자 ID 해시
user_id = request['headers'].get('user-agent', [{}])[0].get('value', '')
hash_value = int(hashlib.md5(user_id.encode()).hexdigest(), 16)
# 10%에게만 새 버전 제공
if hash_value % 100 < 10:
request['uri'] = '/v2.0/' + request['uri']
else:
request['uri'] = '/v1.9/' + request['uri']
return request
모범 사례
1. 에러 처리
def lambda_handler(event, context):
try:
# 비즈니스 로직
result = process_data(event)
return {
'statusCode': 200,
'body': json.dumps(result)
}
except ValueError as e:
# 클라이언트 에러
return {
'statusCode': 400,
'body': json.dumps({'error': str(e)})
}
except Exception as e:
# 서버 에러
logger.error(f"Error: {str(e)}")
# CloudWatch에 메트릭 전송
cloudwatch.put_metric_data(
Namespace='MyApp',
MetricData=[{
'MetricName': 'Errors',
'Value': 1
}]
)
return {
'statusCode': 500,
'body': json.dumps({'error': 'Internal Server Error'})
}
2. 보안
# 환경 변수로 시크릿 분리
DB_HOST = os.environ['DB_HOST']
# Secrets Manager 사용
def get_secret():
secret = secretsmanager.get_secret_value(SecretId='my-db-password')
return json.loads(secret['SecretString'])
# IAM 최소 권한 원칙
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem"
],
"Resource": "arn:aws:dynamodb:region:account:table/MyTable"
}]
}
3. 모니터링
# X-Ray 추적
from aws_xray_sdk.core import xray_recorder
@xray_recorder.capture('process_order')
def process_order(order_id):
# 처리 로직
pass
# 커스텀 메트릭
cloudwatch = boto3.client('cloudwatch')
cloudwatch.put_metric_data(
Namespace='MyApp/Orders',
MetricData=[{
'MetricName': 'OrdersProcessed',
'Value': 1,
'Unit': 'Count',
'Dimensions': [{
'Name': 'Environment',
'Value': 'Production'
}]
}]
)
비용 최적화 전략
1. Lambda 최적화
# 메모리 vs 실행 시간 트레이드오프
256MB: 10초 실행 = $0.00001667
512MB: 5초 실행 = $0.00001667 (동일!)
→ 메모리 증가 = 비용 동일하지만 성능 향상
2. DynamoDB On-Demand
초기 단계:
- On-Demand (예측 불가)
안정화 후:
- Provisioned + Auto Scaling
- Reserved Capacity (1-3년)
→ 최대 70% 절감
3. API Gateway 캐싱
캐시 적용:
- 요청: 100만/일
- 캐시 히트율: 80%
- 비용 절감: 80%
추가 비용:
- 캐시 (0.5GB): $0.02/시간
서버리스 아키텍처는 빠른 개발과 자동 확장을 가능하게 한다.
핵심 패턴
- 모바일 백엔드:
- Cognito (인증) + API Gateway + Lambda + DynamoDB
- 웹사이트:
- S3 (정적) + CloudFront + API Gateway + Lambda
- 마이크로서비스:
- API Gateway (라우팅) + Lambda (서비스) + EventBridge (통신)
- 배치 처리:
- S3 (트리거) + Lambda + Step Functions (오케스트레이션)
- 실시간:
- DynamoDB Streams + Lambda + WebSocket
Share article