Smithy TypeScript API
Smithy는 프로토콜에 구애받지 않는 모델 기반 API 정의 언어입니다.
Smithy TypeScript API 생성기는 Smithy를 서비스 정의에 사용하고 Smithy TypeScript Server SDK를 구현에 활용하는 새로운 API를 생성합니다. 이 생성기는 AWS Lambda에 서비스를 배포하기 위한 CDK 또는 Terraform 인프라스트럭처 코드를 제공하며, AWS API Gateway REST API를 통해 노출됩니다. Smithy 모델에서 자동 코드 생성을 통해 타입 안전한 API 개발을 지원합니다. 생성된 핸들러는 로깅, AWS X-Ray 트레이싱, CloudWatch 메트릭을 포함한 관측성을 위해 AWS Lambda Powertools for TypeScript를 사용합니다.
사용 방법
섹션 제목: “사용 방법”Smithy TypeScript API 생성하기
섹션 제목: “Smithy TypeScript API 생성하기”새로운 Smithy TypeScript API를 두 가지 방법으로 생성할 수 있습니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#api - 필수 매개변수 입력
- framework: smithy
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#api --framework=smithyyarn nx g @aws/nx-plugin:ts#api --framework=smithynpx nx g @aws/nx-plugin:ts#api --framework=smithybunx nx g @aws/nx-plugin:ts#api --framework=smithy어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#api --framework=smithy --dry-runyarn nx g @aws/nx-plugin:ts#api --framework=smithy --dry-runnpx nx g @aws/nx-plugin:ts#api --framework=smithy --dry-runbunx nx g @aws/nx-plugin:ts#api --framework=smithy --dry-run| 매개변수 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| name 필수 | string | - | API의 이름 (필수). 클래스 이름과 파일 경로를 생성하는 데 사용됩니다. |
| framework | trpc | smithy | trpc | 사용할 API 프레임워크. |
| namespace | string | - | Smithy API의 네임스페이스입니다 (smithy 프레임워크에만 적용됨). 기본값은 모노레포 스코프입니다 |
| integrationPattern | isolated | shared | isolated | API에 대한 API Gateway 통합이 생성되는 방식입니다. isolated (기본값) 또는 shared 중에서 선택하세요. |
| auth | iam | cognito | custom | iam | API 인증에 사용할 방법입니다. iam(기본값), cognito 또는 custom 중에서 선택하세요. |
| directory | string | packages | 애플리케이션을 저장할 디렉토리입니다. |
| subDirectory | string | - | 프로젝트가 배치되는 하위 디렉토리입니다. 기본값은 프로젝트 이름입니다. |
| iac | inherit | cdk | terraform | inherit | 선호하는 IaC 공급자입니다. 기본적으로 초기 선택에서 상속됩니다. |
| infra | rest-lambda | http-lambda | none | rest-lambda | 이 API를 배포하는 데 사용할 인프라 유형입니다. |
| preferInstallDependencies | boolean | true | 생성기 실행 후 의존성 설치를 선호할지 여부입니다. 여러 생성기를 일괄 처리할 때 설치를 연기하려면 false로 설정하세요(후속 생성기가 Nx 프로젝트 그래프를 계산할 수 있도록 필요한 경우 설치는 여전히 실행됩니다). 마지막에 한 번만 설치하세요. |
생성기 출력 결과
섹션 제목: “생성기 출력 결과”생성기는 <directory>/<api-name> 디렉토리에 두 개의 관련 프로젝트를 생성합니다:
디렉터리model/ Smithy 모델 프로젝트
- project.json 프로젝트 설정 및 빌드 타겟
- smithy-build.json Smithy 빌드 설정
- build.Dockerfile Smithy 아티팩트 빌드를 위한 Docker 설정
디렉터리src/
- main.smithy 메인 서비스 정의
디렉터리operations/
- echo.smithy 예제 오퍼레이션 정의
디렉터리backend/ TypeScript 백엔드 구현
- project.json 프로젝트 설정 및 빌드 타겟
- rolldown.config.ts 번들 설정
디렉터리src/
- handler.ts AWS Lambda 핸들러
- local-server.ts 로컬 개발 서버
- service.ts 서비스 구현
- context.ts 서비스 컨텍스트 정의
디렉터리operations/
- echo.ts 예제 오퍼레이션 구현
디렉터리generated/ 생성된 TypeScript SDK (빌드 시 생성)
- …
인프라스트럭처
섹션 제목: “인프라스트럭처”이 생성기는 선택한 iacProvider 기반으로 코드형 인프라스트럭처를 생성하므로, packages/common에 관련 CDK 구문 또는 Terraform 모듈을 포함하는 프로젝트가 생성됩니다.
공통 인프라스트럭처 코드 프로젝트 구조는 다음과 같습니다:
디렉터리packages/common/constructs
디렉터리src
디렉터리app/ 프로젝트/생성기 전용 인프라스트럭처 구문
디렉터리apis/
- <project-name>.ts API 배포를 위한 CDK 구문
디렉터리core/
app의 구문에서 재사용되는 일반 구문디렉터리api/
- rest-api.ts REST API 배포를 위한 CDK 구문
- utils.ts API 구문 유틸리티
- index.ts
app에서 구문을 내보내는 진입점
- project.json 프로젝트 빌드 타겟 및 설정
디렉터리packages/common/terraform
디렉터리src
디렉터리app/ 프로젝트/생성기 전용 Terraform 모듈
디렉터리apis/
디렉터리<project-name>/
- <project-name>.tf API 배포를 위한 모듈
디렉터리core/
app의 모듈에서 재사용되는 일반 모듈디렉터리api/
디렉터리rest-api/
- rest-api.tf REST API 배포를 위한 모듈
- project.json 프로젝트 빌드 타겟 및 설정
아키텍처
섹션 제목: “아키텍처”배포된 Smithy API는 다음과 같은 아키텍처를 가지며, API Gateway 스테이지 앞에 AWS WAFv2 Web ACL이 위치합니다:
Smithy API 구현하기
섹션 제목: “Smithy API 구현하기”Smithy에서 오퍼레이션 정의하기
섹션 제목: “Smithy에서 오퍼레이션 정의하기”오퍼레이션은 모델 프로젝트 내 Smithy 파일에 정의됩니다. 메인 서비스 정의는 main.smithy에 있습니다:
$version: "2.0"
namespace your.namespace
use aws.protocols#restJson1use smithy.framework#ValidationException
@title("YourService")@restJson1service YourService { version: "1.0.0" operations: [ Echo, // 여기에 오퍼레이션 추가 ] errors: [ ValidationException ]}개별 오퍼레이션은 operations/ 디렉토리의 별도 파일에 정의됩니다:
$version: "2.0"
namespace your.namespace
@http(method: "POST", uri: "/echo")operation Echo { input: EchoInput output: EchoOutput}
structure EchoInput { @required message: String
foo: Integer bar: String}
structure EchoOutput { @required message: String}TypeScript에서 오퍼레이션 구현하기
섹션 제목: “TypeScript에서 오퍼레이션 구현하기”오퍼레이션 구현은 백엔드 프로젝트의 src/operations/ 디렉토리에 위치합니다. 각 오퍼레이션은 Smithy 모델에서 빌드 시 생성된 TypeScript Server SDK의 생성 타입을 사용하여 구현됩니다.
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input) => { // 비즈니스 로직 구현 return { message: `Echo: ${input.message}` // Smithy 모델 기반 타입 안전성 };};오퍼레이션은 src/service.ts의 서비스 정의에 등록해야 합니다:
import { ServiceContext } from './context.js';import { YourServiceService } from './generated/ssdk/index.js';import { Echo } from './operations/echo.js';// 다른 오퍼레이션 임포트
// 서비스에 오퍼레이션 등록export const Service: YourServiceService<ServiceContext> = { Echo, // 다른 오퍼레이션 추가};서비스 컨텍스트
섹션 제목: “서비스 컨텍스트”context.ts에서 오퍼레이션 간 공유 컨텍스트를 정의할 수 있습니다:
export interface ServiceContext { // 기본 제공 Powertools 트레이서, 로거, 메트릭 tracer: Tracer; logger: Logger; metrics: Metrics; // 공유 의존성, DB 연결 등 추가 dbClient: any; userIdentity: string;}이 컨텍스트는 모든 오퍼레이션 구현에 전달되며 데이터베이스 연결, 설정, 로깅 유틸리티 등을 공유하는 데 사용됩니다.
AWS Lambda Powertools를 통한 관측성
섹션 제목: “AWS Lambda Powertools를 통한 관측성”생성기는 Middy 미들웨어를 통해 자동 컨텍스트 주입이 가능한 구조화된 로깅을 구성합니다.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);컨텍스트를 통해 오퍼레이션 구현에서 로거 참조 가능:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.logger.info('로그 메시지'); // ...};트레이싱
섹션 제목: “트레이싱”captureLambdaHandler 미들웨어를 통해 AWS X-Ray 트레이싱이 자동 구성됩니다.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);오퍼레이션에서 커스텀 서브세그먼트 추가 가능:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { // 새 서브세그먼트 생성 const subsegment = ctx.tracer.getSegment()?.addNewSubsegment('custom-operation'); try { // 로직 구현 } catch (error) { subsegment?.addError(error as Error); throw error; } finally { subsegment?.close(); }};메트릭
섹션 제목: “메트릭”logMetrics 미들웨어를 통해 요청별 CloudWatch 메트릭이 자동 수집됩니다.
export const handler = middy<APIGatewayProxyEvent, APIGatewayProxyResult>() .use(captureLambdaHandler(tracer)) .use(injectLambdaContext(logger)) .use(logMetrics(metrics)) .handler(lambdaHandler);오퍼레이션에서 커스텀 메트릭 추가 가능:
import { MetricUnit } from '@aws-lambda-powertools/metrics';import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { ctx.metrics.addMetric("CustomMetric", MetricUnit.Count, 1); // ...};에러 처리
섹션 제목: “에러 처리”Smithy는 내장 에러 처리를 제공합니다. Smithy 모델에 커스텀 에러 정의 가능:
@error("client")@httpError(400)structure InvalidRequestError { @required message: String}오퍼레이션/서비스에 에러 등록:
operation MyOperation { ... errors: [InvalidRequestError]}TypeScript 구현에서 에러 발생:
import { InvalidRequestError } from '../generated/ssdk/index.js';
export const MyOperation: MyOperationHandler<ServiceContext> = async (input) => { if (!input.requiredField) { throw new InvalidRequestError({ message: "필수 필드 누락" }); }
return { /* 성공 응답 */ };};호출 사용자 액세스
섹션 제목: “호출 사용자 액세스”API가 인증으로 보호되는 경우, 오퍼레이션은 누가 호출하는지 알아야 하는 경우가 많습니다. 권장되는 접근 방식은 핸들러에서 호출자의 신원을 한 번 확인하고 서비스 컨텍스트를 통해 전달하여 특정 오퍼레이션에서 사용하도록 하는 것입니다.
무단 액세스 케이스를 Smithy 오류로 모델링하여 적절한 403 응답으로 직렬화되도록 합니다. 예를 들어 model/src/operations/errors.smithy에서 모델에 추가하고, 신원이 필요한 모든 오퍼레이션에서 참조합니다:
$version: "2.0"
namespace your.namespace
/// Thrown when the calling user cannot be determined@error("client")@httpError(403)structure UnauthorizedError { @required message: String}먼저 src/context.ts에서 서비스 컨텍스트에 확인된 신원을 노출합니다. UnauthorizedError가 핸들러가 아닌 오퍼레이션 내에서 발생하도록(Server SDK가 이를 403으로 직렬화하는 곳) 함수로 제공합니다:
import { Logger } from '@aws-lambda-powertools/logger';import { Metrics } from '@aws-lambda-powertools/metrics';import { Tracer } from '@aws-lambda-powertools/tracer';
export interface Identity { sub: string; username: string;}
/** * Context provided to all operations. */export interface ServiceContext { tracer: Tracer; logger: Logger; metrics: Metrics; getIdentity: () => Promise<Identity>;}다음으로, src/identity.ts에서 리졸버를 작성합니다. 호출자를 확인할 수 없을 때 UnauthorizedError를 발생시킵니다. 구현은 선택한 auth 방법에 따라 다릅니다:
IAM 인증의 경우, API Gateway 이벤트에서 추출한 sub를 사용하여 Cognito에서 호출자를 조회합니다:
import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';import type { APIGatewayProxyEvent } from 'aws-lambda';import { Identity } from './context.js';import { UnauthorizedError } from './generated/ssdk/index.js';
const cognito = new CognitoIdentityProvider();
export const getIdentity = async ( event: APIGatewayProxyEvent,): Promise<Identity> => { const cognitoAuthenticationProvider = event.requestContext?.identity?.cognitoAuthenticationProvider;
let sub: string | undefined = undefined; if (cognitoAuthenticationProvider) { const providerParts = cognitoAuthenticationProvider.split(':'); sub = providerParts[providerParts.length - 1]; }
if (!sub) { throw new UnauthorizedError({ message: 'Unable to determine calling user' }); }
const { Users } = await cognito.listUsers({ // Assumes user pool id is configured in lambda environment UserPoolId: process.env.USER_POOL_ID!, Limit: 1, Filter: `sub="${sub}"`, });
if (!Users || Users.length !== 1) { throw new UnauthorizedError({ message: `No user found with subjectId ${sub}` }); }
return { sub, username: Users[0].Username! };};auth: 'cognito'를 사용하면, API Gateway Cognito User Pools 권한 부여자가 호출자가 Authorization 헤더에 제공하는 JWT를 검증하고 검증된 클레임을 event.requestContext.authorizer.claims의 이벤트에 배치합니다:
import type { APIGatewayProxyEvent } from 'aws-lambda';import { Identity } from './context.js';import { UnauthorizedError } from './generated/ssdk/index.js';
export const getIdentity = async ( event: APIGatewayProxyEvent,): Promise<Identity> => { const claims = event.requestContext?.authorizer?.claims as | Record<string, string> | undefined;
const sub = claims?.sub; const username = claims?.username;
if (!sub || !username) { throw new UnauthorizedError({ message: 'Unable to determine calling user' }); }
return { sub, username };};그런 다음 src/handler.ts에서 리졸버를 컨텍스트에 연결합니다:
import { Service } from './service.js';import { getIdentity } from './identity.js';// ...const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, getIdentity: () => getIdentity(event),});이제 오퍼레이션에서 확인된 신원을 사용할 수 있습니다. 예를 들어 src/operations/echo.ts에서:
import { ServiceContext } from '../context.js';import { Echo as EchoOperation } from '../generated/ssdk/index.js';
export const Echo: EchoOperation<ServiceContext> = async (input, ctx) => { const identity = await ctx.getIdentity(); return { message: `${identity.username} says ${input.message}` };};빌드 및 코드 생성
섹션 제목: “빌드 및 코드 생성”Smithy 모델 프로젝트는 Docker를 사용하여 Smithy 아티팩트를 빌드하고 TypeScript Server SDK를 생성합니다:
pnpm nx build <model-project>yarn nx build <model-project>npx nx build <model-project>bunx nx build <model-project>이 과정은 다음을 수행합니다:
- Smithy 모델 컴파일 및 검증
- Smithy 모델에서 OpenAPI 명세 생성
- 타입 안전 오퍼레이션 인터페이스가 포함된 TypeScript Server SDK 생성
dist/<model-project>/build/에 빌드 아티팩트 출력
백엔드 프로젝트는 컴파일 시 생성된 SDK를 자동 복사합니다:
pnpm nx copy-ssdk <backend-project>yarn nx copy-ssdk <backend-project>npx nx copy-ssdk <backend-project>bunx nx copy-ssdk <backend-project>번들 타겟
섹션 제목: “번들 타겟”제너레이터는 Rolldown을 사용하여 배포 패키지를 생성하는 bundle 타겟을 자동으로 구성합니다:
pnpm nx bundle <project-name>yarn nx bundle <project-name>npx nx bundle <project-name>bunx nx bundle <project-name>Rolldown 구성은 rolldown.config.ts에서 확인할 수 있으며, 생성할 각 번들별로 엔트리가 존재합니다. 정의된 경우 Rolldown은 여러 번들을 병렬로 생성하는 작업을 관리합니다.
로컬 개발
섹션 제목: “로컬 개발”생성기는 핫 리로딩이 가능한 로컬 개발 서버를 구성합니다:
pnpm nx serve <backend-project>yarn nx serve <backend-project>npx nx serve <backend-project>bunx nx serve <backend-project>Smithy API 배포하기
섹션 제목: “Smithy API 배포하기”생성기는 선택한 iacProvider 기반으로 CDK 또는 Terraform 인프라스트럭처를 생성합니다.
API 배포를 위한 CDK 구문은 common/constructs 폴더에 있습니다:
import { MyApi } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // 스택에 API 추가 const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}이 설정은 다음을 구성합니다:
- Smithy 서비스를 위한 AWS Lambda 함수
- 함수 트리거로 API Gateway REST API
- IAM 역할 및 권한
- CloudWatch 로그 그룹
- X-Ray 트레이싱 설정
API 배포를 위한 Terraform 모듈은 common/terraform 폴더에 있습니다.
API 모듈은 공유 S3 자산 버킷에 Lambda 배포 zip을 스테이징합니다 — 자세한 내용은 Terraform 인프라스트럭처 가이드를 참조하세요. 배포당 한 번 core/asset-bucket 모듈을 인스턴스화하고 bucket_name 출력을 asset_bucket_name 입력을 통해 모든 API / Lambda 모듈에 전달하세요:
module "asset_bucket" { source = "../../common/terraform/src/core/asset-bucket"}
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Lambda 함수 환경 변수 env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# 추가 IAM 정책 additional_iam_policy_statements = [ # API에 필요한 추가 권한 ]
tags = local.common_tags}이 설정은 다음을 구성합니다:
- Smithy API를 서비스하는 AWS Lambda 함수
- 함수 트리거로 API Gateway REST API
- IAM 역할 및 권한
- CloudWatch 로그 그룹
- X-Ray 트레이싱 설정
- CORS 설정
Terraform 모듈은 여러 출력을 제공합니다:
# API 엔드포인트 접근output "api_url" { value = module.my_api.stage_invoke_url}
# Lambda 함수 상세 정보output "lambda_function_name" { value = module.my_api.lambda_function_name}WAF
섹션 제목: “WAF”REST API의 경우, 생성된 구성은 기본적으로 AWS WAFv2 Web ACL을 API Gateway 스테이지와 연결합니다. Web ACL은 AWS 관리형 기본 규칙 세트(AWSManagedRulesCommonRuleSet 및 AWSManagedRulesKnownBadInputsRuleSet)를 사용하여 OWASP Top 10을 포함한 일반적인 웹 공격으로부터 보호합니다. WAF 요청 로그는 CloudWatch Logs 그룹에 기록됩니다.
생성된 rest-api 구성을 편집하여 규칙을 추가, 제거 또는 조정할 수 있습니다(예: rate-based rules 또는 추가 관리형 규칙 그룹 추가).
옵트아웃하려면(예: 자체 Web ACL을 연결하려는 경우) enableWaf를 false로 설정하세요:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), enableWaf: false,});옵트아웃하려면(예: 자체 Web ACL을 연결하려는 경우) enable_waf를 false로 설정하세요:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name enable_waf = false}액세스 로깅
섹션 제목: “액세스 로깅”REST API의 경우, 생성된 인프라는 기본적으로 액세스 로깅을 활성화하여 요청당 하나의 구조화된 JSON 라인을 전용 CloudWatch Logs 그룹에 기록합니다. 로그 그룹은 고객 관리형 KMS 키로 암호화되며 1년 동안 보관됩니다.
API Gateway는 계정 수준의 CloudWatch Logs 역할을 사용하여 액세스 로그를 작성합니다. 이 역할은 AWS::ApiGateway::Account 설정에 구성되며, 이는 리전당 계정당 싱글톤입니다 — 리전의 모든 REST API에 대해 하나의 역할만 존재합니다. 독립적으로 배포된 여러 스택에서 이를 안전하게 관리하기 위해, 생성된 인프라는:
- 작동하는 역할이 아직 설정되지 않은 경우에만 공유 CloudWatch Logs 역할을 생성하고 계정에 구성하므로, 배포가 다른 스택이 소유한 역할을 덮어쓰지 않습니다.
- 해체 시 계정 설정을 그대로 유지하므로, 하나의 스택을 삭제해도 리전의 다른 REST API에 대한 로깅이 비활성화되지 않습니다.
계정 역할은 ApiGatewayAccount 구성으로 관리되며, ApiGatewayAccount.ensure(scope)를 통해 해결되는 스택 범위 싱글톤입니다. 각 REST API의 스테이지는 이에 의존하며, 역할은 Lambda 기반 사용자 지정 리소스에 의해 구성됩니다.
API를 구성할 때 deployOptions를 전달하여 액세스 로그 형식을 사용자 지정할 수 있습니다:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), deployOptions: { accessLogFormat: AccessLogFormat.clf(), },});계정 역할은 core/api/api-gateway-account 모듈에 의해 관리되며, 생성된 API 모듈에 의해 인스턴스화됩니다. 이는 계정을 멱등적으로 구성하며 terraform destroy 시 재설정되지 않습니다.
생성된 API 모듈의 aws_api_gateway_stage 리소스에서 access_log_settings 블록을 편집하여 액세스 로그 형식을 사용자 지정할 수 있습니다.
REST/HTTP API CDK 구문은 각 작업에 대한 통합을 정의하기 위한 타입 안전 인터페이스를 제공하도록 구성됩니다.
기본 통합
섹션 제목: “기본 통합”정적 defaultIntegrations를 사용하여 각 작업에 대해 개별 AWS Lambda 함수를 정의하는 기본 패턴을 활용할 수 있습니다:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});Terraform 모듈은 단일 Lambda 함수를 사용하는 라우터 패턴을 자동으로 적용합니다. 추가 구성이 필요하지 않습니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# 모듈은 모든 API 작업을 처리하는 단일 Lambda 함수를 자동 생성합니다 tags = local.common_tags}통합 접근
섹션 제목: “통합 접근”API 구문의 integrations 속성을 통해 타입 안전 방식으로 기본 AWS Lambda 함수에 접근할 수 있습니다. 예를 들어 API에 sayHello 작업이 정의된 경우 이 함수에 권한을 추가하려면 다음과 같이 할 수 있습니다:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello는 API에 정의된 작업으로 타입이 지정됩니다api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));API가 shared 패턴을 사용하는 경우 공유 라우터 Lambda는 api.integrations.$router로 노출됩니다:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');Terraform 라우터 패턴에서는 단일 Lambda 함수만 존재합니다. 모듈 출력을 통해 접근할 수 있습니다:
# 단일 Lambda 함수에 추가 권한 부여resource "aws_iam_role_policy" "additional_permissions" { name = "additional-api-permissions" role = module.my_api.lambda_execution_role_name
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}기본 옵션 사용자 정의
섹션 제목: “기본 옵션 사용자 정의”withDefaultOptions 메서드를 사용하여 기본 통합 생성 시 사용되는 옵션을 사용자 정의할 수 있습니다. 예를 들어 모든 Lambda 함수를 VPC에 배치하려면:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});VPC 구성과 같은 옵션을 사용자 정의하려면 생성된 Terraform 모듈을 수정해야 합니다. 모든 Lambda 함수에 VPC 지원을 추가하려면:
# VPC 변수 추가variable "vpc_subnet_ids" { description = "Lambda 함수용 VPC 서브넷 ID 목록" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "Lambda 함수용 VPC 보안 그룹 ID 목록" type = list(string) default = []}
# Lambda 함수 리소스 업데이트resource "aws_lambda_function" "api_lambda" { # ... 기존 구성 ...
# VPC 구성 추가 vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}다음과 같이 VPC 구성으로 모듈을 사용합니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# VPC 구성 vpc_subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] vpc_security_group_ids = [aws_security_group.lambda_sg.id]
tags = local.common_tags}작업별 옵션 사용자 정의
섹션 제목: “작업별 옵션 사용자 정의”특정 작업에 대한 기본 통합을 생성할 때 사용되는 옵션을 사용자 정의하려면(다른 작업에 영향을 주지 않고) withOperationOptions 메서드를 사용할 수 있습니다. 예를 들어 한 작업에 대해서만 Lambda 함수 타임아웃을 늘리려면:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOperationOptions({ sayHello: { timeout: Duration.seconds(60), }, }) .build(),});
// 선택한 작업은 기본 통합으로 유지되므로 여전히 그에 따라 타입이 지정됩니다:api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ ... }));지정한 옵션은 기본 통합 옵션(및 withDefaultOptions를 통해 설정된 옵션)과 병합됩니다. withOverrides를 통해 교체한 작업에 대해서는 옵션을 지정할 수 없습니다. 이러한 작업은 더 이상 기본 통합을 사용하지 않기 때문입니다.
withOperationOptions와 withOverrides 모두에서 동일한 작업을 대상으로 하는 경우, 호출 순서에 관계없이 타입 오류가 발생합니다.
Terraform에서 특정 작업에 대한 옵션을 사용자 정의하려면 생성된 Terraform 모듈을 편집하여 작업별 개별 Lambda 함수를 구성해야 합니다 (아래 명시적 통합 섹션 참조).
통합 재정의
섹션 제목: “통합 재정의”withOverrides 메서드를 사용하여 특정 작업에 대한 통합을 재정의할 수 있습니다. 각 재정의는 HTTP 또는 REST API에 적합한 CDK 통합 구문으로 타입이 지정된 integration 속성을 지정해야 합니다. withOverrides 메서드도 타입 안전합니다. 예를 들어 getDocumentation API를 외부 웹사이트 호스팅 문서로 재정의하려면:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});api.integrations.getDocumentation을 통해 접근 시 재정의된 통합에는 더 이상 handler 속성이 없습니다.
추가 속성을 통합에 추가하면 다른 유형의 통합을 추상화하면서도 타입 안전성을 유지할 수 있습니다. 예를 들어 REST API용 S3 통합을 생성한 후 특정 작업에 대한 버킷을 참조하려면:
const storageBucket = new Bucket(this, 'Bucket', { ... });
const apiGatewayRole = new Role(this, 'ApiGatewayS3Role', { assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),});
storageBucket.grantRead(apiGatewayRole);
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getFile: { bucket: storageBucket, integration: new AwsIntegration({ service: 's3', integrationHttpMethod: 'GET', path: `${storageBucket.bucketName}/{fileName}`, options: { credentialsRole: apiGatewayRole, requestParameters: { 'integration.request.path.fileName': 'method.request.querystring.fileName', }, integrationResponses: [{ statusCode: '200' }], }, }), options: { requestParameters: { 'method.request.querystring.fileName': true, }, methodResponses: [{ statusCode: '200', }], } }, }) .build(),});
// 이후 다른 파일에서 정의한 bucket 속성에 타입 안전 방식으로 접근 가능api.integrations.getFile.bucket.grantRead(...);인증자 재정의
섹션 제목: “인증자 재정의”통합에 options를 제공하여 Cognito 인증과 같은 특정 메서드 옵션을 재정의할 수 있습니다. 예를 들어 getDocumentation 작업에 Cognito 인증을 사용하려면:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), options: { authorizer: new CognitoUserPoolsAuthorizer(...) // REST용 또는 HttpUserPoolAuthorizer (HTTP API) } }, }) .build(),});명시적 통합
섹션 제목: “명시적 통합”기본 통합 대신 각 작업에 직접 통합을 제공할 수 있습니다. 이는 각 작업이 다른 유형의 통합을 사용해야 하거나 새 작업 추가 시 타입 오류를 수신하려는 경우 유용합니다:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});Terraform에서 작업별 명시적 통합을 사용하려면 기본 프록시 통합을 작업별 특정 통합으로 교체하도록 생성된 앱별 모듈을 수정해야 합니다.
packages/common/terraform/src/app/apis/my-api/my-api.tf 편집:
- 기본 프록시 경로 제거 (예:
resource "aws_apigatewayv2_route" "proxy_routes") - 단일 Lambda 함수를 작업별 개별 함수로 교체
- 동일 ZIP 번들 재사용하여 작업별 특정 통합 및 경로 생성:
# 기본 단일 Lambda 함수 제거 resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... 나머지 구성 }
# 기본 프록시 통합 제거 resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... 나머지 구성 }
# 기본 프록시 경로 제거 resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... 나머지 구성 }
# 동일 번들 사용 작업별 Lambda 함수 추가 resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # 이 작업용 특정 핸들러 runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # 이 작업용 특정 핸들러 runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# 작업별 특정 통합 추가 resource "aws_apigatewayv2_integration" "say_hello_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.say_hello_handler.invoke_arn payload_format_version = "2.0" timeout_milliseconds = 30000 }
resource "aws_apigatewayv2_integration" "get_documentation_integration" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET" }
# 작업별 특정 경로 추가 resource "aws_apigatewayv2_route" "say_hello_route" { api_id = module.http_api.api_id route_key = "POST /sayHello" target = "integrations/${aws_apigatewayv2_integration.say_hello_integration.id}" authorization_type = "AWS_IAM" }
resource "aws_apigatewayv2_route" "get_documentation_route" { api_id = module.http_api.api_id route_key = "GET /documentation" target = "integrations/${aws_apigatewayv2_integration.get_documentation_integration.id}" authorization_type = "NONE" }
# 각 함수에 Lambda 권한 추가 resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }# 기본 단일 Lambda 함수 제거 resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... 나머지 구성 }
# 기본 프록시 통합 제거 resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... 나머지 구성 }
# 기본 프록시 경로 제거 resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... 나머지 구성 }
# 동일 번들 사용 작업별 Lambda 함수 추가 resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # 이 작업용 특정 핸들러 runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # 이 작업용 특정 핸들러 runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = var.env }
tags = var.tags }
# 작업별 리소스 및 메서드 추가 resource "aws_api_gateway_resource" "say_hello_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "sayHello" }
resource "aws_api_gateway_method" "say_hello_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = "POST" authorization = "AWS_IAM" }
resource "aws_api_gateway_integration" "say_hello_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = aws_api_gateway_method.say_hello_method.http_method
integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.say_hello_handler.invoke_arn }
resource "aws_api_gateway_resource" "get_documentation_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "documentation" }
resource "aws_api_gateway_method" "get_documentation_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = "GET" authorization = "NONE" }
resource "aws_api_gateway_integration" "get_documentation_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = aws_api_gateway_method.get_documentation_method.http_method
integration_http_method = "GET" type = "HTTP" uri = "https://example.com/documentation" }
# 새 통합에 종속성 업데이트~ resource "aws_api_gateway_deployment" "api_deployment" { rest_api_id = module.rest_api.api_id
depends_on = [ aws_api_gateway_integration.lambda_integration, aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ]
lifecycle { create_before_destroy = true }
triggers = { redeployment = sha1(jsonencode([ aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ])) } }
# 각 함수에 Lambda 권한 추가 resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }통합 패턴
섹션 제목: “통합 패턴”생성된 CDK API 구문은 두 가지 통합 패턴을 지원합니다:
isolated는 작업당 하나의 Lambda 함수를 생성합니다. 이는 생성된 API의 기본값입니다.shared는 단일 기본 라우터 Lambda를 생성하고 특정 통합을 재정의하지 않는 한 모든 작업에 재사용합니다.
isolated는 작업별로 더 세밀한 권한과 구성을 제공합니다. shared는 선택적 재정의를 여전히 허용하면서 Lambda 및 API Gateway 통합 확산을 줄입니다.
예를 들어 pattern을 'shared'로 설정하면 통합당 하나씩이 아닌 단일 함수를 생성합니다:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { ... return IntegrationBuilder.rest({ pattern: 'shared', ... }); };}Terraform 모듈은 자동으로 라우터 패턴을 사용합니다 - 이것이 기본이자 유일하게 지원되는 접근 방식입니다. 생성된 모듈은 모든 API 작업을 처리하는 단일 Lambda 함수를 생성합니다.
기본 모듈을 인스턴스화하여 라우터 패턴을 얻을 수 있습니다:
# 기본 라우터 패턴 - 모든 작업을 처리하는 단일 Lambda 함수module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# 단일 Lambda 함수가 모든 작업을 자동으로 처리 tags = local.common_tags}코드 생성
섹션 제목: “코드 생성”Smithy에 정의된 오퍼레이션을 타입 안전 통합을 위해 CDK 구문에 메타데이터를 제공하기 위해 코드 생성을 사용합니다.
공통 구문의 project.json에 generate:<ApiName>-metadata 타겟이 추가되어 packages/common/constructs/src/generated/my-api/metadata.gen.ts와 같은 파일을 생성합니다. 이 파일은 빌드 시 생성되므로 버전 관리에서 제외됩니다.
접근 권한 부여 (IAM 전용)
섹션 제목: “접근 권한 부여 (IAM 전용)”IAM 인증을 선택한 경우 grantInvokeAccess 메서드로 API 접근 권한 부여 가능:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);# API 호출을 허용하는 IAM 정책 생성resource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Smithy API 호출 허용 정책"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# IAM 역할에 정책 연결resource "aws_iam_role_policy_attachment" "api_invoke_access" { role = aws_iam_role.authenticated_user_role.name policy_arn = aws_iam_policy.api_invoke_policy.arn}Smithy API 호출하기
섹션 제목: “Smithy API 호출하기”React 웹사이트에서 API를 호출하려면 connection 생성기를 사용하여 Smithy 모델에서 타입 안전 클라이언트를 생성할 수 있습니다.
connection 생성기를 사용하여 이 프로젝트를 워크스페이스의 다른 프로젝트와 통합할 수 있습니다. 다음은 이 프로젝트와 관련된 연결입니다: