メインコンテンツまでスキップ

その他各種推奨実装パターン

アプリケーションタイプに関らず、AWS Lambda 上での稼働を鑑みた推奨実装パターンを紹介します。

AWS SDKを利用する場合の推奨構成

aws ディレクトリに、AWS SDK (boto3) のラッパークラスを実装して配置し、モジュールとして利用します。

メリットは以下のようになります。

  • 再利用性向上
    • サーバーレスでは特にAWSサービスの呼出しが多い
  • テスト便宜性・保守性向上
    • Mock 化のしやすさ
    • AWS SDK のバージョンアップや仕様変更による影響範囲を最小化
  • コード可読性向上

AWS SDK のみならず、外部API呼び出しやライブラリを利用する際は、同様な構成にすることで上記のメリットを享受できます。

ラッパークラスの例

例えば、以下は S3 にアクセスする部分のラッパークラスの実装例です。

import {
ListObjectsV2Command,
type ListObjectsV2CommandInput,
PutObjectCommand,
type PutObjectCommandInput,
S3Client,
} from '@aws-sdk/client-s3'

const client = new S3Client({ region: 'us-east-1' })

export class S3 {
private s3Client: S3Client

public constructor() {
this.s3Client = new S3Client({ region: 'us-east-1' })
}

public async putObject(bucket: string, key: string, body: any, metadata = {}) {
const payload = {
Bucket: bucket,
Key: key,
Body: body,
} as PutObjectCommandInput
if (Object.keys(metadata).length) {
payload.Metadata = metadata
}
const command = new PutObjectCommand(payload)
const uploadRes = await this.s3Client.send(command)
return uploadRes
}

public async getObjectAsString(bucket: string, fileKey: string) {
const command = new GetObjectCommand({
Bucket: bucket,
Key: fileKey,
})
const object = await client.send(command)
return await object.Body?.transformToString()
}

public async listObjects(bucket: string, prefix: string, continuationToken = '') {
const input = {
Bucket: bucket,
Prefix: prefix,
} as ListObjectsV2CommandInput
if (continuationToken) {
input.ContinuationToken = continuationToken
}
const command = new ListObjectsV2Command(input)
const response = await this.s3Client.send(command)

return response
}
}

API エラーハンドリング

利用する API ルーティングライブラリによって方法変わってくることがありますが、方針としてはエラーハンドラーとカスタムエラークラスを実装して利用します。本カタログでは、以下のようなカスタムエラーを作成する形にしています。

  • src/sample-api/errors/SampleException.ts
import { HTTPException } from 'hono/http-exception'
import type { StatusCode } from '~/sample-api/exception'

export class SampleException extends HTTPException {
public errorCode: string
public statusCode: number

public constructor(params: { status: StatusCode; errorCode: string; message: string; cause?: Error }) {
super(params.status, { message: params.message })
super.cause = params.cause
this.errorCode = params.errorCode

Error.captureStackTrace(this, this.constructor)
}
}

APIのロジックの中で、エラーレスポンスを返したい場合は、以下のように例外(エラー)を投げます。

  • src/sample-api/services/UserService.ts
  public async getUserFromRdb(userId: number) {
const result = await this.userDao.findById(userId)

if (!result) {
throw new SampleException({
status: StatusCode.NOT_FOUND,
errorCode: 'USER_NOT_FOUND',
message: 'User does not exist in RDB',
})
}
return result
}

ここで投げられた例外は、以下の ExceptionHandler にて拾われ、API のエラーレスポンスとして返されます。

  • src/sample-api/errors/ExceptionHandler.ts
import { SampleException, StatusCode } from '~/sample-api/exception'
import { logger } from '~/sample-api/logger'

export const handleException = (err: Error): Response => {
if (err instanceof SampleException) {
const body = JSON.stringify({
code: err.errorCode,
message: err.message,
})

return new Response(body, {
status: err.status,
headers: {
'content-type': 'application/json; charset=UTF-8',
},
})
}

logger.error('## error thrown in handleException', { err })

const body = JSON.stringify({
code: 'UNEXPECTED_ERROR',
message: err.message,
})

return new Response(body, {
status: StatusCode.INTERNAL_SERVER_ERROR,
headers: {
'content-type': 'application/json; charset=UTF-8',
},
})
}

Logger

本カタログで推奨する JSON 構造化ログを出力するために、Powertools for AWS Lambda (Python) の Logger を利用したログ出力方法を説明します。

使い方

以下のようにインスタンス化して利用します。ニーズに項目の増減を行うこともでき、Lambda エントリーポイントの開始時点または logger クラスを別途定義し、初期化と設定(contextの反映)を行うようにしてください。

  • src/sample-api/logger/Logger.ts
  • src/sample-event/logger/Logger.ts
import { Logger } from '@aws-lambda-powertools/logger'

const logger = new Logger()

export { logger }
  • src/sample-event/index.ts
import { logger } from '~/sample-event/logger'

export const handler = async (event: S3Event, context: Context): Promise<void> => {
logger.addContext(context)
// ...
}

デフォルトでは、以下のようなログが出力されます。

{
"level": "INFO",
"location": "create_news:19",
"message": "Created new news record - {'news_type': 'sports', 'title': 'sample_news_title', 'content': 'sample_news_conten'}",
"timestamp": "2025-04-20 01:51:45,781+0000",
"service": "service_undefined",
"cold_start": true,
"function_name": "tester-function",
"function_memory_size": 256,
"function_arn": "invoked_function_arn",
"function_request_id": "aws_request_id"
}

CloudWatch Logs でのログ検索

上記のフォーマットを元に、CloudWatch Logs では以下のようにログ検索を行うことができます。

# ログレベルを指定して検索
{ $.level = "ERROR" }

# Request ID を指定して検索
{ $.requestId = “xxx” }

CloudWatch Logs で検索すると、以下のような結果になります。