API Smithy vers Base de Données Relationnelle
Le générateur connection relie une API Smithy à un projet de Base de Données Relationnelle, en injectant un client Prisma dans le contexte du service afin que toutes les implémentations d’opérations puissent accéder à la base de données.
Prérequis
Section intitulée « Prérequis »Avant d’utiliser ce générateur, assurez-vous d’avoir :
- Un projet API TypeScript Smithy (généré avec
ts#apien utilisant--framework=smithy) - Un projet
ts#rdb
Utilisation
Section intitulée « Utilisation »Exécuter le Générateur
Section intitulée « Exécuter le Générateur »- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - connection - Remplissez les paramètres requis
- Cliquez sur
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:connectionVous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
pnpm nx g @aws/nx-plugin:connection --dry-runyarn nx g @aws/nx-plugin:connection --dry-runnpx nx g @aws/nx-plugin:connection --dry-runbunx nx g @aws/nx-plugin:connection --dry-runSélectionnez votre projet backend d’API Smithy comme source et votre projet de base de données relationnelle comme cible.
| Paramètre | Type | Par défaut | Description |
|---|---|---|---|
| sourceProject Requis | string | - | Le projet source |
| targetProject Requis | string | - | Le projet cible auquel se connecter |
| sourceComponent | string | - | Le composant source depuis lequel se connecter (nom du composant, chemin relatif à la racine du projet source, ou identifiant du générateur). Utilisez '.' pour sélectionner explicitement le projet comme source. |
| targetComponent | string | - | Le composant cible auquel se connecter (nom du composant, chemin relatif à la racine du projet cible, ou identifiant du générateur). Utilisez '.' pour sélectionner explicitement le projet comme cible. |
| preferInstallDependencies | boolean | true | Indique s'il faut privilégier l'installation des dépendances après l'exécution du générateur. Définir à false pour différer l'installation lors de l'exécution de plusieurs générateurs en lot (une installation s'exécute quand même si nécessaire pour que les générateurs suivants puissent calculer le graphe de projet Nx) ; installer une seule fois à la fin. |
Sortie du Générateur
Section intitulée « Sortie du Générateur »Le générateur modifie trois fichiers existants dans votre backend d’API Smithy :
Répertoirepackages/api/src
- context.ts propriété
dbajoutée àServiceContext - handler.ts Client Prisma créé dans
lambdaHandler, passé àserviceHandler.handle - local-server.ts Client Prisma créé dans le gestionnaire de requêtes, passé à
serviceHandler.handle
- context.ts propriété
De plus, il met à jour la cible serve-local de l’API pour démarrer automatiquement la base de données.
Comment ça Fonctionne
Section intitulée « Comment ça Fonctionne »ServiceContext
Section intitulée « ServiceContext »Le générateur ajoute une propriété db typée à ServiceContext dans context.ts :
import { getPrisma as getMyDb } from ':my-scope/my-db';
export interface ServiceContext { tracer: Tracer; logger: Logger; metrics: Metrics; myDb: Awaited<ReturnType<typeof getMyDb>>;}Lambda Handler
Section intitulée « Lambda Handler »Le client Prisma est instancié dans lambdaHandler et passé via le contexte du service :
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);};Utiliser la Base de Données dans les Opérations
Section intitulée « Utiliser la Base de Données dans les Opérations »Accédez à db depuis le contexte dans vos implémentations d’opérations :
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 };};Bases de Données Multiples
Section intitulée « Bases de Données Multiples »Exécuter à nouveau le générateur avec une cible différente ajoute la deuxième base de données à côté de la première. Les deux clients sont ajoutés à ServiceContext et instanciés dans 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,});Infrastructure
Section intitulée « Infrastructure »Pour permettre à votre API de se connecter à la base de données lors de l’exécution, les fonctions Lambda de l’API doivent être déployées dans le même VPC que la base de données et bénéficier d’un accès réseau et IAM.
Dans votre stack d’application, déployez l’API dans le même VPC que la base de données, puis appelez allowDefaultPortFrom et grantConnect pour ouvrir le chemin réseau et accorder la permission IAM rds-db:connect à chaque gestionnaire Lambda :
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);});Déployez les fonctions Lambda de l’API dans un sous-réseau privé avec sortie, et non dans un sous-réseau privé isolé. Lors de l’exécution, getPrisma() récupère les détails de connexion à la base de données depuis AWS AppConfig, qui est un point de terminaison de service AWS public nécessitant un accès Internet sortant.
Déployez l’API dans le même VPC que la base de données, accordez-lui rds-db:connect via additional_iam_policy_statements, et ouvrez le chemin réseau avec une paire de règles de groupe de sécurité. Les ressources aws_vpc.main et aws_subnet sont définies dans le guide de déploiement de la base de données :
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}" ] } ]}
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"}Déployez les fonctions Lambda de l’API dans des sous-réseaux privés avec sortie, et non dans des sous-réseaux privés isolés. appconfig_application_id/appconfig_application_arn proviennent de l’application AppConfig de configuration d’exécution partagée déclarée une fois dans votre module racine, et non du module de base de données — leur transmission définit RUNTIME_CONFIG_APP_ID sur les fonctions Lambda et leur accorde un accès en lecture à l’application. Incluez l’espace de noms database lors de son instanciation afin que l’entrée de configuration d’exécution du module de base de données soit déployée :
module "runtime_config_appconfig" { source = "../../common/terraform/src/core/runtime-config/appconfig"
application_name = "my-app-runtime-config" namespaces = ["connection", "agentcore", "database"]}Exigences SSL lors de la Connexion sans RDS Proxy
Section intitulée « Exigences SSL lors de la Connexion sans RDS Proxy »Pour les connexions directes au cluster Aurora depuis les runtimes Lambda Node.js 20 ou ultérieurs, chargez le bundle CA Amazon RDS en définissant NODE_EXTRA_CA_CERTS :
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" }}Pour plus de détails, consultez les exigences SSL/TLS pour les connexions Amazon RDS d’AWS Lambda et la documentation TLS d’Amazon RDS Proxy. Lors de l’utilisation de RDS Proxy, vous n’avez pas besoin de configurer le bundle CA RDS dans votre fonction Lambda.
Développement Local
Section intitulée « Développement Local »Le générateur applique la même injection de client Prisma dans le gestionnaire de requêtes dans local-server.ts :
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>Cela démarre à la fois l’API et la base de données locale. La variable d’environnement LOCAL_DEV=true est définie automatiquement, de sorte que le client Prisma se connecte à la base de données Docker locale au lieu d’Aurora.