Smithy API からリレーショナルデータベースへの接続
connection ジェネレーターは、Smithy API を リレーショナルデータベース プロジェクトに接続し、Prisma クライアントをサービスコンテキストに注入することで、すべての操作実装がデータベースにアクセスできるようにします。
このジェネレーターを使用する前に、以下を用意してください:
- Smithy TypeScript API プロジェクト(
ts#apiを--framework=smithyで生成) ts#rdbプロジェクト
ジェネレーターの実行
Section titled “ジェネレーターの実行”- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - connection - 必須パラメータを入力
- クリック
Generate
pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connectionソースとして Smithy API バックエンドプロジェクトを選択し、ターゲットとしてリレーショナルデータベースプロジェクトを選択します。
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
| sourceProject 必須 | string | - | ソース プロジェクト |
| targetProject 必須 | string | - | 接続先のターゲット プロジェクト |
| sourceComponent | string | - | 接続元のソース コンポーネント (コンポーネント名、ソース プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをソースとして明示的に選択するには '.' を使用します。 |
| targetComponent | string | - | 接続先のターゲット コンポーネント (コンポーネント名、ターゲット プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをターゲットとして明示的に選択するには '.' を使用します。 |
| preferInstallDependencies | boolean | true | ジェネレーター実行後に依存関係のインストールを優先するかどうか。複数のジェネレーターをバッチ処理する際にインストールを延期する場合はfalseに設定します(後続のジェネレーターがNxプロジェクトグラフを計算できるよう、必要に応じてインストールは実行されます)。最後に一度だけインストールします。 |
ジェネレーターの出力
Section titled “ジェネレーターの出力”ジェネレーターは、Smithy API バックエンドの既存の3つのファイルを変更します:
Directorypackages/api/src
- context.ts
ServiceContextにdbプロパティが追加されます - handler.ts
lambdaHandler内で Prisma クライアントが作成され、serviceHandler.handleに渡されます - local-server.ts リクエストハンドラー内で Prisma クライアントが作成され、
serviceHandler.handleに渡されます
- context.ts
さらに、API の dev ターゲットを更新して、データベースを自動的に起動するようにします。
動作の仕組み
Section titled “動作の仕組み”ServiceContext
Section titled “ServiceContext”ジェネレーターは、context.ts の ServiceContext に型付きの db プロパティを追加します:
import { getPrisma as getMyDb } from ':my-scope/my-db';
export interface ServiceContext { tracer: Tracer; logger: Logger; metrics: Metrics; myDb: Awaited<ReturnType<typeof getMyDb>>;}Lambda ハンドラー
Section titled “Lambda ハンドラー”Prisma クライアントは lambdaHandler 内でインスタンス化され、サービスコンテキストを通じて渡されます:
import { getPrisma as getMyDb } from ':my-scope/my-db';
export const lambdaHandler = async (event: APIGatewayProxyEvent) => { const httpRequest = convertEvent(event); const myDb = await getMyDb(); const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, myDb, }); return convertVersion1Response(httpResponse);};操作内でのデータベースの使用
Section titled “操作内でのデータベースの使用”操作実装内で、コンテキストから db にアクセスします:
import { ListUsersOperationInput, ListUsersOperationOutput } from '../generated/ssdk/index.js';import { ServiceContext } from '../context.js';
export const listUsers = async ( input: ListUsersOperationInput, ctx: ServiceContext,): Promise<ListUsersOperationOutput> => { const users = await ctx.myDb.user.findMany(); return { users };};複数のデータベース
Section titled “複数のデータベース”異なるターゲットで再度ジェネレーターを実行すると、最初のデータベースと並んで2番目のデータベースが追加されます。両方のクライアントが ServiceContext に追加され、handler.ts でインスタンス化されます:
export interface ServiceContext { tracer: Tracer; logger: Logger; metrics: Metrics; myDb: Awaited<ReturnType<typeof getMyDb>>; otherDb: Awaited<ReturnType<typeof getOtherDb>>;}const myDb = await getMyDb();const otherDb = await getOtherDb();const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, myDb, otherDb,});インフラストラクチャ
Section titled “インフラストラクチャ”実行時にAPIがデータベースに接続できるようにするには、API Lambda関数をデータベースと同じVPCにデプロイし、ネットワークおよびIAMアクセスを付与する必要があります。
アプリケーションスタックで、APIをデータベースと同じVPCにデプロイし、allowDefaultPortFromとgrantConnectを呼び出して、ネットワークパスを開き、各Lambdaハンドラーに IAM rds-db:connect権限を付与します:
import { MyDatabase } from ':my-scope/common-constructs';
const db = new MyDatabase(this, 'Db', { vpc, ... });
const api = new MyApi(this, 'Api', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, }) .build(),});
Object.entries(api.integrations).forEach(([operation, integration]) => { db.allowDefaultPortFrom(integration.handler, `Allow ${operation} to connect to the database`); db.grantConnect(integration.handler);});API Lambda関数は、プライベート分離サブネットではなく、エグレス付きプライベートサブネットにデプロイしてください。実行時に、getPrisma()はAWS AppConfigからデータベース接続の詳細を取得しますが、これはアウトバウンドインターネットアクセスを必要とするパブリックAWSサービスエンドポイントです。
APIをデータベースと同じVPCにデプロイし、additional_iam_policy_statementsを介してrds-db:connectを付与し、セキュリティグループルールのペアでネットワークパスを開きます。aws_vpc.mainとaws_subnetリソースは、データベースデプロイメントガイドで定義されています:
module "my_database" { source = "../../common/terraform/src/app/dbs/my-database" vpc_id = aws_vpc.main.id database_subnet_ids = aws_subnet.database[*].id lambda_subnet_ids = aws_subnet.private[*].id}
module "api" { source = "../../common/terraform/src/app/apis/my-api" enable_vpc = true vpc_id = aws_vpc.main.id subnet_ids = aws_subnet.private[*].id
additional_iam_policy_statements = [ { Effect = "Allow" Action = ["rds-db:connect"] Resource = [ "arn:aws:rds-db:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:dbuser:${module.my_database.connect_resource_id}/${module.my_database.database_runtime_user}" ] }, { Effect = "Allow" Action = [ "appconfig:StartConfigurationSession", "appconfig:GetLatestConfiguration" ] Resource = [ "${module.runtime_config_appconfig.application_arn}/*" ] } ]
env = { RUNTIME_CONFIG_APP_ID = module.runtime_config_appconfig.application_id }}
resource "aws_vpc_security_group_ingress_rule" "api_to_database" { description = "Allow the API Lambda functions to connect to the database" security_group_id = module.my_database.security_group_id referenced_security_group_id = module.api.security_group_id from_port = module.my_database.cluster_port to_port = module.my_database.cluster_port ip_protocol = "tcp"}
resource "aws_vpc_security_group_egress_rule" "api_to_database" { description = "Allow outbound traffic from the API Lambda functions to the database" security_group_id = module.api.security_group_id referenced_security_group_id = module.my_database.security_group_id from_port = module.my_database.cluster_port to_port = module.my_database.cluster_port ip_protocol = "tcp"}API Lambda関数は、プライベート分離サブネットではなく、エグレス付きプライベートサブネットにデプロイしてください。appconfig_application_id/appconfig_application_arnは、データベースモジュールからではなく、ルートモジュールで一度宣言された共有ランタイム設定 AppConfigアプリケーションから取得します。これらを渡すことで、Lambda関数にRUNTIME_CONFIG_APP_IDが設定され、アプリケーションへの読み取りアクセスが付与されます。データベースモジュールのランタイム設定エントリがデプロイされるように、インスタンス化する際にdatabase名前空間を含めてください:
module "runtime_config_appconfig" { source = "../../common/terraform/src/core/runtime-config/appconfig"
application_name = "my-app-runtime-config" namespaces = ["connection", "agentcore", "database"]}RDS Proxy を使用せずに接続する場合の SSL 要件
Section titled “RDS Proxy を使用せずに接続する場合の SSL 要件”Node.js 20以降のLambdaランタイムからAuroraクラスターに直接接続する場合、NODE_EXTRA_CA_CERTSを設定してAmazon RDS CAバンドルを読み込みます:
const api = new Api(this, 'Api', { integrations: Api.defaultIntegrations(this) .withDefaultOptions({ environment: { NODE_EXTRA_CA_CERTS: '/var/runtime/ca-cert.pem', }, }) .build(),});module "api" { source = "..." ...
environment_variables = { NODE_EXTRA_CA_CERTS = "/var/runtime/ca-cert.pem" }}詳細については、AWS LambdaのAmazon RDS接続のSSL/TLS要件およびAmazon RDS ProxyのTLSドキュメントを参照してください。RDS Proxyを使用する場合、Lambda関数でRDS CAバンドルを設定する必要はありません。
ローカル開発
Section titled “ローカル開発”ジェネレーターは、local-server.ts のリクエストハンドラー内でも同じ Prisma クライアントの注入を適用します:
import { getPrisma as getMyDb } from ':my-scope/my-db';
const server = createServer(async function (req, res) { const httpRequest = convertRequest(req); const myDb = await getMyDb(); const httpResponse = await serviceHandler.handle(httpRequest, { tracer, logger, metrics, myDb, }); return writeResponse(httpResponse, res);});pnpm nx dev <api-project-name>yarn nx dev <api-project-name>npx nx dev <api-project-name>bunx nx dev <api-project-name>これにより、API とローカルデータベースの両方が起動します。LOCAL_DEV=true 環境変数が自動的に設定されるため、Prisma クライアントは Aurora ではなくローカルの Docker データベースに接続します。