Migración desde AWS PDK
Esta guía te lleva paso a paso a través de un ejemplo de migración de un proyecto AWS PDK al Nx Plugin para AWS, además de proporcionar orientación general sobre este tema.
Migrar al Nx Plugin para AWS ofrece los siguientes beneficios frente a PDK:
- Construcciones más rápidas
- Más fácil de usar (UI y CLI)
- Compatible con vibe-coding (¡prueba nuestro servidor MCP!)
- Tecnologías más modernas
- Desarrollo local de APIs y sitios web
- Más control (modifica archivos provistos para adaptarlos a tu caso de uso)
- ¡Y mucho más!
Ejemplo de Migración: Aplicación de Lista de Compras
Sección titulada «Ejemplo de Migración: Aplicación de Lista de Compras»En esta guía, usaremos la Aplicación de Lista de Compras del Tutorial de PDK como nuestro proyecto objetivo para migrar. Sigue los pasos de ese tutorial para crear el proyecto objetivo si deseas replicar el proceso.
La aplicación de lista de compras consiste en los siguientes tipos de proyecto PDK:
MonorepoTsProject
TypeSafeApiProject
CloudscapeReactTsWebsiteProject
InfrastructureTsProject
Crear Espacio de Trabajo
Sección titulada «Crear Espacio de Trabajo»Para comenzar, crearemos un nuevo espacio de trabajo para nuestro proyecto nuevo. Aunque más extremo que una migración in situ, este enfoque nos da el resultado final más limpio. Crear un espacio de trabajo Nx es equivalente a usar el MonorepoTsProject
de PDK:
npx create-nx-workspace@21.4.1 shopping-list --pm=pnpm --preset=@aws/nx-plugin@0.50.0 --iacProvider=CDK --ci=skip
npx create-nx-workspace@21.4.1 shopping-list --pm=yarn --preset=@aws/nx-plugin@0.50.0 --iacProvider=CDK --ci=skip
npx create-nx-workspace@21.4.1 shopping-list --pm=npm --preset=@aws/nx-plugin@0.50.0 --iacProvider=CDK --ci=skip
npx create-nx-workspace@21.4.1 shopping-list --pm=bun --preset=@aws/nx-plugin@0.50.0 --iacProvider=CDK --ci=skip
Abre el directorio shopping-list
que crea este comando en tu IDE favorito.
Migrar la API
Sección titulada «Migrar la API»El TypeSafeApiProject
utilizado en la aplicación de lista de compras hizo uso de:
- Smithy como lenguaje de modelado
- TypeScript para implementar operaciones
- Generación de hooks TypeScript para integración con un sitio web en React
Por lo tanto, podemos usar el generador ts#smithy-api
para proporcionar funcionalidad equivalente.
Generar una API Smithy en TypeScript
Sección titulada «Generar una API Smithy en TypeScript»Ejecuta el generador ts#smithy-api
para configurar tu proyecto de API en packages/api
:
- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)
en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - ts#smithy-api
- Complete los parámetros requeridos
- name: api
- namespace: com.aws
- auth: IAM
- Haga clic en
Generate
pnpm nx g @aws/nx-plugin:ts#smithy-api --name=api --namespace=com.aws --auth=IAM --no-interactive
yarn nx g @aws/nx-plugin:ts#smithy-api --name=api --namespace=com.aws --auth=IAM --no-interactive
npx nx g @aws/nx-plugin:ts#smithy-api --name=api --namespace=com.aws --auth=IAM --no-interactive
bunx nx g @aws/nx-plugin:ts#smithy-api --name=api --namespace=com.aws --auth=IAM --no-interactive
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
pnpm nx g @aws/nx-plugin:ts#smithy-api --name=api --namespace=com.aws --auth=IAM --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#smithy-api --name=api --namespace=com.aws --auth=IAM --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#smithy-api --name=api --namespace=com.aws --auth=IAM --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#smithy-api --name=api --namespace=com.aws --auth=IAM --no-interactive --dry-run
Notarás que esto genera un proyecto model
, así como un proyecto backend
. El proyecto model
contiene tu modelo Smithy, y backend
contiene la implementación del servidor.
El backend utiliza el Smithy Server Generator for TypeScript. Exploraremos esto más adelante.
Migrar el Modelo Smithy
Sección titulada «Migrar el Modelo Smithy»Ahora que tenemos la estructura básica para nuestro proyecto de API Smithy, podemos migrar el modelo:
-
Elimina los archivos Smithy de ejemplo generados en
packages/api/model/src
-
Copia tu modelo desde el directorio
packages/api/model/src/main/smithy
del proyecto PDK al directoriopackages/api/model/src
de tu nuevo proyecto. -
Actualiza el nombre del servicio y namespace en
smithy-build.json
para que coincidan con la aplicación PDK:smithy-build.json "plugins": {"openapi": {"service": "com.aws#MyApi",... -
Actualiza el servicio en
main.smithy
para agregar el errorValidationException
, requerido cuando se usa el Smithy TypeScript Server SDK.main.smithy use smithy.framework#ValidationException/// My Shopping List API@restJson1service MyApi {version: "1.0"operations: [GetShoppingListsPutShoppingListDeleteShoppingList]errors: [BadRequestErrorNotAuthorizedErrorInternalFailureErrorValidationException]} -
Agrega un archivo
extensions.smithy
enpackages/api/model/src
donde definiremos un trait que proporciona información de paginación al cliente generado:extensions.smithy $version: "2"namespace com.awsuse smithy.openapi#specificationExtension@trait@specificationExtension(as: "x-cursor")structure cursor {inputToken: Stringenabled: Boolean} -
Agrega el nuevo trait
@cursor
a la operaciónGetShoppingLists
enget-shopping-lists.smithy
:operations/get-shopping-lists.smithy @readonly@http(method: "GET", uri: "/shopping-list")@paginated(inputToken: "nextToken", outputToken: "nextToken", pageSize: "pageSize", items: "shoppingLists")@cursor(inputToken: "nextToken")@handler(language: "typescript")operation GetShoppingLists {input := with [PaginatedInputMixin] {@httpQuery("shoppingListId")shoppingListId: ShoppingListId}Cualquier operación
@paginated
también debe usar@cursor
si estás usando el generador de clientes proporcionado por el Nx Plugin para AWS (a través del generadorapi-connection
). -
Finalmente, elimina el trait
@handler
de todas las operaciones ya que no es compatible con el Nx Plugin para AWS. Al usarts#smithy-api
, no necesitamos las construcciones CDK de funciones lambda generadas automáticamente ni los objetivos de bundling generados por este trait, ya que usamos un único bundle para todas las funciones lambda.
En este punto, ejecutemos un build para verificar los cambios en el modelo y asegurarnos de tener código de servidor generado para trabajar. Habrá algunos errores en el proyecto backend (@shopping-list/api
), pero los abordaremos a continuación.
pnpm nx run-many --target build
yarn nx run-many --target build
npx nx run-many --target build
bunx nx run-many --target build
Migrar los Manejadores Lambda
Sección titulada «Migrar los Manejadores Lambda»Puedes considerar el proyecto api/backend
como algo equivalente al proyecto api/handlers/typescript
de Type Safe API.
Una de las principales diferencias entre Type Safe API y el generador ts#smithy-api
es que los manejadores se implementan usando el Smithy Server Generator for TypeScript, en lugar de los wrappers de manejadores generados por Type Safe API (encontrados en el proyecto api/generated/typescript/runtime
).
Los manejadores lambda de la aplicación de lista de compras dependen del paquete @aws-sdk/client-dynamodb
, así que instalémoslo primero:
pnpm add -w @aws-sdk/client-dynamodb
yarn add @aws-sdk/client-dynamodb
npm install --legacy-peer-deps @aws-sdk/client-dynamodb
bun install @aws-sdk/client-dynamodb
Luego, copiemos el archivo handlers/src/dynamo-client.ts
del proyecto PDK a backend/src/operations
para que esté disponible para nuestros manejadores.
Para migrar los manejadores, puedes seguir estos pasos generales:
-
Copia el manejador desde el directorio
packages/api/handlers/typescript/src
de tu proyecto PDK al directoriopackages/api/backend/src/operations
de tu nuevo proyecto. -
Elimina las importaciones de
my-api-typescript-runtime
e importa el tipo de operación desde el SDK de servidor TypeScript generado, así como elServiceContext
:import {deleteShoppingListHandler,DeleteShoppingListChainedHandlerFunction,INTERCEPTORS,Response,LoggingInterceptor,} from 'myapi-typescript-runtime';import { DeleteShoppingList as DeleteShoppingListOperation } from '../generated/ssdk/index.js';import { ServiceContext } from '../context.js'; -
Elimina la exportación del wrapper del manejador
export const handler = deleteShoppingListHandler(...INTERCEPTORS,deleteShoppingList,); -
Actualiza la firma de tu manejador de operación para usar el SSDK:
export const deleteShoppingList: DeleteShoppingListChainedHandlerFunction = async (request) => {export const DeleteShoppingList: DeleteShoppingListOperation<ServiceContext> = async (input, ctx) => { -
Reemplaza el uso de
LoggingInterceptor
conctx.logger
. (También aplica para métricas y tracing interceptors):LoggingInterceptor.getLogger(request).info('...');ctx.logger.info('...'); -
Actualiza referencias a parámetros de entrada. Como el SSDK provee tipos que coinciden exactamente con tu modelo Smithy (en lugar de agrupar parámetros path/query/header separados del body), actualiza cualquier referencia de entrada:
const shoppingListId = request.input.requestParameters.shoppingListId;const shoppingListId = input.shoppingListId; -
Elimina el uso de
Response
. En su lugar, devolvemos objetos simples en el SSDK.return Response.success({ shoppingListId });return { shoppingListId };Tampoco lanzamos ni devolvemos
Response
, en su lugar lanzamos los errores generados por el SSDK:throw Response.badRequest({ message: 'oh no' });return Response.badRequest({ message: 'oh no' });import { BadRequestError } from '../generated/ssdk/index.js';throw new BadRequestError({ message: 'oh no' }); -
Actualiza las importaciones para usar sintaxis ESM, agregando la extensión
.js
a las importaciones relativas. -
Agrega la operación a
service.ts
service.ts import { ServiceContext } from './context.js';import { MyApiService } from './generated/ssdk/index.js';import { DeleteShoppingList } from './operations/delete-shopping-list.js';import { GetShoppingLists } from './operations/get-shopping-lists.js';import { PutShoppingList } from './operations/put-shopping-list.js';// Registra las operaciones en el servicio aquíexport const Service: MyApiService<ServiceContext> = {PutShoppingList,GetShoppingLists,DeleteShoppingList,};
Migración del manejador de lista de compras
Eliminar Lista de Compras
import { DeleteItemCommand } from '@aws-sdk/client-dynamodb';import { deleteShoppingListHandler, DeleteShoppingListChainedHandlerFunction, INTERCEPTORS, Response, LoggingInterceptor,} from 'myapi-typescript-runtime';import { ddbClient } from './dynamo-client';
/** * Manejador type-safe para la operación DeleteShoppingList */export const deleteShoppingList: DeleteShoppingListChainedHandlerFunction = async (request) => { LoggingInterceptor.getLogger(request).info( 'Start DeleteShoppingList Operation', );
const shoppingListId = request.input.requestParameters.shoppingListId; await ddbClient.send( new DeleteItemCommand({ TableName: 'shopping_list', Key: { shoppingListId: { S: shoppingListId, }, }, }), );
return Response.success({ shoppingListId, });};
/** * Punto de entrada para el manejador AWS Lambda de la operación DeleteShoppingList. * deleteShoppingListHandler envuelve el manejador type-safe y gestiona entradas/salidas */export const handler = deleteShoppingListHandler( ...INTERCEPTORS, deleteShoppingList,);
import { DeleteItemCommand } from '@aws-sdk/client-dynamodb';import { ddbClient } from './dynamo-client.js';import { DeleteShoppingList as DeleteShoppingListOperation } from '../generated/ssdk/index.js';import { ServiceContext } from '../context.js';
/** * Manejador type-safe para la operación DeleteShoppingList */export const DeleteShoppingList: DeleteShoppingListOperation<ServiceContext> = async (input, ctx) => { ctx.logger.info( 'Start DeleteShoppingList Operation', );
const shoppingListId = input.shoppingListId; await ddbClient.send( new DeleteItemCommand({ TableName: 'shopping_list', Key: { shoppingListId: { S: shoppingListId!, }, }, }), );
return { shoppingListId, };};
Obtener Listas de Compras
import { DynamoDBClient, QueryCommand, QueryCommandInput, ScanCommand, ScanCommandInput } from '@aws-sdk/client-dynamodb';import { getShoppingListsHandler, GetShoppingListsChainedHandlerFunction, INTERCEPTORS, Response, LoggingInterceptor, ShoppingList,} from 'myapi-typescript-runtime';import { ddbClient } from './dynamo-client';
/** * Manejador type-safe para la operación GetShoppingLists */export const getShoppingLists: GetShoppingListsChainedHandlerFunction = async (request) => { LoggingInterceptor.getLogger(request).info('Start GetShoppingLists Operation');
const nextToken = request.input.requestParameters.nextToken; const pageSize = request.input.requestParameters.pageSize; const shoppingListId = request.input.requestParameters.shoppingListId; const commandInput: ScanCommandInput | QueryCommandInput = { TableName: 'shopping_list', ConsistentRead: true, Limit: pageSize, ExclusiveStartKey: nextToken ? fromToken(nextToken) : undefined, ...(shoppingListId ? { KeyConditionExpression: 'shoppingListId = :shoppingListId', ExpressionAttributeValues: { ':shoppingListId': { S: request.input.requestParameters.shoppingListId!, }, }, } : {}), }; const response = await ddbClient.send(shoppingListId ? new QueryCommand(commandInput) : new ScanCommand(commandInput));
return Response.success({ shoppingLists: (response.Items || []) .map<ShoppingList>(item => ({ shoppingListId: item.shoppingListId.S!, name: item.name.S!, shoppingItems: JSON.parse(item.shoppingItems.S || '[]'), })), nextToken: response.LastEvaluatedKey ? toToken(response.LastEvaluatedKey) : undefined, });};
/** * Decodificar un token stringificado * @param token token pasado a la solicitud paginada */const fromToken = <T>(token?: string): T | undefined => token ? (JSON.parse(Buffer.from(decodeURIComponent(token), 'base64').toString()) as T) : undefined;
/** * Codificar detalles de paginación en un token stringificado opaco * @param paginationToken detalles del token de paginación */const toToken = <T>(paginationToken?: T): string | undefined => paginationToken ? encodeURIComponent(Buffer.from(JSON.stringify(paginationToken)).toString('base64')) : undefined;
/** * Punto de entrada para el manejador AWS Lambda de la operación GetShoppingLists. * getShoppingListsHandler envuelve el manejador type-safe y gestiona entradas/salidas */export const handler = getShoppingListsHandler(...INTERCEPTORS, getShoppingLists);
import { QueryCommand, QueryCommandInput, ScanCommand, ScanCommandInput } from '@aws-sdk/client-dynamodb';import { ddbClient } from './dynamo-client.js';import { GetShoppingLists as GetShoppingListsOperation, ShoppingList } from '../generated/ssdk/index.js';import { ServiceContext } from '../context.js';
/** * Manejador type-safe para la operación GetShoppingLists */export const GetShoppingLists: GetShoppingListsOperation<ServiceContext> = async (input, ctx) => { ctx.logger.info('Start GetShoppingLists Operation');
const nextToken = input.nextToken; const pageSize = input.pageSize; const shoppingListId = input.shoppingListId; const commandInput: ScanCommandInput | QueryCommandInput = { TableName: 'shopping_list', ConsistentRead: true, Limit: pageSize, ExclusiveStartKey: nextToken ? fromToken(nextToken) : undefined, ...(shoppingListId ? { KeyConditionExpression: 'shoppingListId = :shoppingListId', ExpressionAttributeValues: { ':shoppingListId': { S: input.shoppingListId!, }, }, } : {}), }; const response = await ddbClient.send(shoppingListId ? new QueryCommand(commandInput) : new ScanCommand(commandInput));
return { shoppingLists: (response.Items || []) .map<ShoppingList>(item => ({ shoppingListId: item.shoppingListId.S!, name: item.name.S!, shoppingItems: JSON.parse(item.shoppingItems.S || '[]'), })), nextToken: response.LastEvaluatedKey ? toToken(response.LastEvaluatedKey) : undefined, };};
/** * Decodificar un token stringificado * @param token token pasado a la solicitud paginada */const fromToken = <T>(token?: string): T | undefined => token ? (JSON.parse(Buffer.from(decodeURIComponent(token), 'base64').toString()) as T) : undefined;
/** * Codificar detalles de paginación en un token stringificado opaco * @param paginationToken detalles del token de paginación */const toToken = <T>(paginationToken?: T): string | undefined => paginationToken ? encodeURIComponent(Buffer.from(JSON.stringify(paginationToken)).toString('base64')) : undefined;
Agregar Lista de Compras
import { randomUUID } from 'crypto';import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';import { putShoppingListHandler, PutShoppingListChainedHandlerFunction, INTERCEPTORS, Response, LoggingInterceptor,} from 'myapi-typescript-runtime';import { ddbClient } from './dynamo-client';
/** * Manejador type-safe para la operación PutShoppingList */export const putShoppingList: PutShoppingListChainedHandlerFunction = async (request) => { LoggingInterceptor.getLogger(request).info('Start PutShoppingList Operation');
const shoppingListId = request.input.body.shoppingListId ?? randomUUID(); await ddbClient.send(new PutItemCommand({ TableName: 'shopping_list', Item: { shoppingListId: { S: shoppingListId, }, name: { S: request.input.body.name, }, shoppingItems: { S: JSON.stringify(request.input.body.shoppingItems || []), }, }, }));
return Response.success({ shoppingListId, });};
/** * Punto de entrada para el manejador AWS Lambda de la operación PutShoppingList. * putShoppingListHandler envuelve el manejador type-safe y gestiona entradas/salidas */export const handler = putShoppingListHandler(...INTERCEPTORS, putShoppingList);
import { randomUUID } from 'crypto';import { PutItemCommand } from '@aws-sdk/client-dynamodb';import { ddbClient } from './dynamo-client.js';import { PutShoppingList as PutShoppingListOperation } from '../generated/ssdk/index.js';import { ServiceContext } from '../context.js';
/** * Manejador type-safe para la operación PutShoppingList */export const PutShoppingList: PutShoppingListOperation<ServiceContext> = async (input, ctx) => { ctx.logger.info('Start PutShoppingList Operation');
const shoppingListId = input.shoppingListId ?? randomUUID(); await ddbClient.send(new PutItemCommand({ TableName: 'shopping_list', Item: { shoppingListId: { S: shoppingListId, }, name: { S: input.name!, }, shoppingItems: { S: JSON.stringify(input.shoppingItems || []), }, }, }));
return { shoppingListId, };};
Generamos el proyecto Smithy API con el nombre api
inicialmente para que se agregue a packages/api
y sea consistente con el proyecto PDK. Como nuestra API Smithy ahora define service MyApi
en lugar de service Api
, necesitamos actualizar cualquier instancia de getApiServiceHandler
con getMyApiServiceHandler
.
Realiza este cambio en handler.ts
:
import { getApiServiceHandler } from './generated/ssdk/index.js'; import { getMyApiServiceHandler } from './generated/ssdk/index.js';
process.env.POWERTOOLS_METRICS_NAMESPACE = 'Api';process.env.POWERTOOLS_SERVICE_NAME = 'Api';
const tracer = new Tracer();const logger = new Logger();const metrics = new Metrics();
const serviceHandler = getApiServiceHandler(Service); const serviceHandler = getMyApiServiceHandler(Service);
Y en local-server.ts
:
import { getApiServiceHandler } from './generated/ssdk/index.js';import { getMyApiServiceHandler } from './generated/ssdk/index.js';
const PORT = 3001;
const tracer = new Tracer();const logger = new Logger();const metrics = new Metrics();
const serviceHandler = getApiServiceHandler(Service);const serviceHandler = getMyApiServiceHandler(Service);
Además, actualiza packages/api/backend/project.json
y modifica metadata.apiName
a my-api
:
"metadata": { "generator": "ts#smithy-api", "apiName": "api", "apiName": "my-api", "auth": "IAM", "modelProject": "@shopping-list/api-model", "ports": [3001] },
Verificar con un Build
Sección titulada «Verificar con un Build»Ahora podemos construir el proyecto para verificar que la migración ha funcionado hasta ahora:
pnpm nx run-many --target build
yarn nx run-many --target build
npx nx run-many --target build
bunx nx run-many --target build
Migrar el Sitio Web
Sección titulada «Migrar el Sitio Web»El CloudscapeReactTsWebsiteProject
utilizado en la aplicación de lista de compras configuró un sitio web React con CloudScape y autenticación Cognito integrada.
Este tipo de proyecto utilizaba create-react-app
, que ahora está obsoleto. Para migrar el sitio web en esta guía, usaremos el generador ts#react-website
, que emplea tecnologías más modernas y soportadas, específicamente Vite.
Como parte de la migración, también cambiaremos del React Router configurado por PDK a TanStack Router, que añade mayor seguridad de tipos al enrutamiento del sitio web.
Generar un sitio web React
Sección titulada «Generar un sitio web React»Ejecuta el generador ts#react-website
para configurar tu proyecto de sitio web en packages/website
:
- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)
en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - ts#react-website
- Complete los parámetros requeridos
- name: website
- Haga clic en
Generate
pnpm nx g @aws/nx-plugin:ts#react-website --name=website --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website --name=website --no-interactive
npx nx g @aws/nx-plugin:ts#react-website --name=website --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website --name=website --no-interactive
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
pnpm nx g @aws/nx-plugin:ts#react-website --name=website --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website --name=website --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website --name=website --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website --name=website --no-interactive --dry-run
Añadir autenticación Cognito
Sección titulada «Añadir autenticación Cognito»El generador de sitios web React anterior no incluye autenticación Cognito por defecto como CloudscapeReactTsWebsiteProject
, en su lugar se añade explícitamente mediante el generador ts#react-website#auth
.
- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)
en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - ts#react-website#auth
- Complete los parámetros requeridos
- project: website
- cognitoDomain: shopping-list
- Haga clic en
Generate
pnpm nx g @aws/nx-plugin:ts#react-website#auth --project=website --cognitoDomain=shopping-list --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website#auth --project=website --cognitoDomain=shopping-list --no-interactive
npx nx g @aws/nx-plugin:ts#react-website#auth --project=website --cognitoDomain=shopping-list --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website#auth --project=website --cognitoDomain=shopping-list --no-interactive
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
pnpm nx g @aws/nx-plugin:ts#react-website#auth --project=website --cognitoDomain=shopping-list --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website#auth --project=website --cognitoDomain=shopping-list --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website#auth --project=website --cognitoDomain=shopping-list --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website#auth --project=website --cognitoDomain=shopping-list --no-interactive --dry-run
Esto añade componentes React que gestionan las redirecciones necesarias para asegurar que los usuarios inicien sesión usando la interfaz alojada de Cognito. También añade un constructo CDK para desplegar los recursos de Cognito en packages/common/constructs
, llamado UserIdentity
.
Conectar el sitio web a la API
Sección titulada «Conectar el sitio web a la API»En PDK podías pasar proyectos Projen configurados entre sí para activar la integración automática. Esto se usaba en la aplicación de lista de compras para configurar la integración del sitio web con la API.
Con el Plugin Nx para AWS, la integración de API se realiza mediante el generador api-connection
. A continuación, usamos este generador para que nuestro sitio web pueda invocar nuestra API Smithy:
- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)
en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - api-connection
- Complete los parámetros requeridos
- sourceProject: website
- targetProject: api
- Haga clic en
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=website --targetProject=api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=website --targetProject=api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=website --targetProject=api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=website --targetProject=api --no-interactive
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=website --targetProject=api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=website --targetProject=api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=website --targetProject=api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=website --targetProject=api --no-interactive --dry-run
Esto genera los proveedores de cliente y objetivos de construcción necesarios para que tu sitio web llame a tu API mediante un cliente TypeScript generado.
Añadir dependencia de AWS Northstar
Sección titulada «Añadir dependencia de AWS Northstar»El CloudscapeReactTsWebsiteProject
incluía automáticamente una dependencia de @aws-northstar/ui
que se usa en nuestra aplicación de lista de compras, así que la añadimos aquí:
pnpm add -w @aws-northstar/ui
yarn add @aws-northstar/ui
npm install --legacy-peer-deps @aws-northstar/ui
bun install @aws-northstar/ui
Mover componentes y páginas
Sección titulada «Mover componentes y páginas»La aplicación de lista de compras tiene un componente llamado CreateItem
, y dos páginas, ShoppingList
y ShoppingLists
. Migraremos estos elementos al nuevo sitio web, haciendo ajustes por el uso de TanStack Router y el generador de código cliente TypeScript del Plugin Nx para AWS.
-
Copia
packages/website/src/components/CreateItem/index.tsx
del proyecto PDK a la misma ubicación en el nuevo proyecto. -
Copia
packages/website/src/pages/ShoppingLists/index.tsx
apackages/website/src/routes/index.tsx
, ya queShoppingLists
es nuestra página principal y usamos enrutamiento basado en archivos con TanStack Router. -
Copia
packages/website/src/pages/ShoppingList/index.tsx
apackages/website/src/routes/$shoppingListId.tsx
, ya queShoppingList
es la página que queremos mostrar en la ruta/:shoppingListId
.
Notarás algunos errores de compilación en tu IDE, necesitaremos hacer algunos cambios más para adaptarnos al nuevo framework, como se detalla a continuación.
Migrar de React Router a TanStack Router
Sección titulada «Migrar de React Router a TanStack Router»Al usar enrutamiento basado en archivos, podemos usar el servidor de desarrollo local para generar automáticamente la configuración de rutas. Iniciemos el servidor local:
pnpm nx serve-local website
yarn nx serve-local website
npx nx serve-local website
bunx nx serve-local website
Verás algunos errores, pero el servidor local debería iniciarse en el puerto 4200
, junto con el servidor Smithy local en el puerto 3001
.
Sigue estos pasos en ambos archivos routes/index.tsx
y routes/$shoppingListId.tsx
para migrar a TanStack Router:
-
Añade
createFileRoute
para registrar cada ruta:import { createFileRoute } from "@tanstack/react-router";...export default ShoppingLists;export const Route = createFileRoute('/')({component: ShoppingLists,});import { createFileRoute } from "@tanstack/react-router";...export default ShoppingList;export const Route = createFileRoute('/$shoppingListId')({component: ShoppingList,});Al guardar, los errores de tipos con
createFileRoute
desaparecerán. -
Reemplaza el hook
useNavigate
.Actualiza la importación:
import { useNavigate } from 'react-router-dom';import { useNavigate } from '@tanstack/react-router';Actualiza las llamadas al método
navigate
para usar rutas tipadas:navigate(`/${cell.shoppingListId}`);navigate({to: '/$shoppingListId',params: { shoppingListId: cell.shoppingListId },}); -
Reemplaza el hook
useParams
.Elimina la importación:
import { useParams } from 'react-router-dom';Actualiza las llamadas a
useParams
con el hook provisto por laRoute
. ¡Ahora son tipados!const { shoppingListId } = useParams();const { shoppingListId } = Route.useParams();
Corregir importaciones de componentes
Sección titulada «Corregir importaciones de componentes»Como nuestras rutas están en otra ubicación, necesitamos ajustar la importación de CreateItem
en ambos archivos:
import CreateItem from "../../components/CreateItem";import CreateItem from "../components/CreateItem";
El AppLayoutContext
también tiene una ubicación diferente:
import { AppLayoutContext } from "../../layouts/App";import { AppLayoutContext } from "../components/AppLayout";
Migrar al cliente TypeScript generado
Sección titulada «Migrar al cliente TypeScript generado»Casi listos. Ahora migraremos al cliente TypeScript generado por el Plugin Nx para AWS:
-
Importa el nuevo cliente generado:
import {ShoppingList,usePutShoppingList,useDeleteShoppingList,useGetShoppingLists,} from "myapi-typescript-react-query-hooks";import { ShoppingList } from "../generated/my-api/types.gen";import { useMyApi } from "../hooks/useMyApi";import { useInfiniteQuery, useMutation } from "@tanstack/react-query";En
routes/$shoppingListId.tsx
, importaShoppingList
como_ShoppingList
desdetypes.gen
. -
Instancia los nuevos hooks de TanStack Query:
const getShoppingLists = useGetShoppingLists({ pageSize: PAGE_SIZE });const putShoppingList = usePutShoppingList();const deleteShoppingList = useDeleteShoppingList();const api = useMyApi();const getShoppingLists = useInfiniteQuery(api.getShoppingLists.infiniteQueryOptions({ pageSize: PAGE_SIZE },{ getNextPageParam: (res) => res.nextToken },),);const putShoppingList = useMutation(api.putShoppingList.mutationOptions());const deleteShoppingList = useMutation(api.deleteShoppingList.mutationOptions(),); -
Elimina el wrapper
<operation>RequestContent
en llamadas a operaciones:await putShoppingList.mutateAsync({putShoppingListRequestContent: {name: item,},});
Migrar de TanStack Query v4 a v5
Sección titulada «Migrar de TanStack Query v4 a v5»Quedan algunos errores por diferencias entre v4 y v5:
-
Reemplaza
isLoading
porisPending
en mutaciones:putShoppingList.isLoadingputShoppingList.isPending -
Suprime el error de tipo en
InfiniteQueryTable
:<InfiniteQueryTablequery={getShoppingLists}query={getShoppingLists as any}
Visitar el sitio web local
Sección titulada «Visitar el sitio web local»Ahora puedes acceder al sitio en http://localhost:4200/
¡El sitio debería cargarse correctamente! Si tienes una tabla DynamoDB llamada shopping_list
en la región y credenciales AWS locales con acceso, funcionará completamente. Si no, migraremos la infraestructura después.
Migración de páginas de lista de compras
Migrar la Infraestructura
Sección titulada «Migrar la Infraestructura»El último proyecto que necesitamos migrar para nuestra aplicación de lista de compras es el InfrastructureTsProject
. Este es un proyecto CDK en TypeScript, para el cual el equivalente en el Nx Plugin para AWS es el generador ts#infra
.
Al igual que los proyectos Projen, PDK también proporcionaba constructs CDK de los que dependen estos proyectos. También migraremos la aplicación de lista de compras desde estos constructs CDK, en favor de los generados por el Nx Plugin para AWS.
Generar un proyecto de infraestructura TypeScript CDK
Sección titulada «Generar un proyecto de infraestructura TypeScript CDK»Ejecuta el generador ts#infra
para configurar tu proyecto de infraestructura en packages/infra
:
- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)
en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - ts#infra
- Complete los parámetros requeridos
- name: infra
- Haga clic en
Generate
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
También puede realizar una ejecución en seco para ver qué archivos se cambiarían
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
Migrar la infraestructura CDK
Sección titulada «Migrar la infraestructura CDK»La aplicación de lista de compras PDK instanciaba los siguientes constructs dentro de la pila de aplicación CDK:
DatabaseConstruct
para la tabla DynamoDB que almacena las listas de comprasUserIdentity
para recursos de Cognito, importado directamente desde PDKMyApi
para desplegar la API Smithy, que usaba el constructo CDK TypeScript generado con integraciones type-safe, dependiendo internamente del constructo CDKTypeSafeRestApi
de PDK.Website
para desplegar el sitio web, envolviendo el constructo CDKStaticWebsite
de PDK.
A continuación, migraremos cada uno de estos al nuevo proyecto.
Copiar la pila de aplicación
Sección titulada «Copiar la pila de aplicación»Copia packages/infra/src/stacks/application-stack.ts
de la aplicación PDK de lista de compras a la misma ubicación exacta en tu nuevo proyecto. Verás algunos errores de TypeScript que abordaremos más adelante.
Copiar el constructo Database
Sección titulada «Copiar el constructo Database»La aplicación PDK de lista de compras tenía un constructo Database
en packages/src/constructs/database.ts
. Cópialo a la misma ubicación exacta en tu nuevo proyecto.
Dado que el Nx Plugin para AWS usa Checkov para pruebas de seguridad, que es un poco más estricto que PDK Nag, también necesitamos agregar algunas supresiones:
import { suppressRules } from ':shopping-list/common-constructs';...suppressRules( this.shoppingListTable, ['CKV_AWS_28', 'CKV_AWS_119'], 'Backup and KMS key not required for this project',);
En application-stack.ts
, actualiza la importación para DatabaseConstruct
para usar sintaxis ESM:
import { DatabaseConstruct } from '../constructs/database';import { DatabaseConstruct } from '../constructs/database.js';
Migrar el constructo UserIdentity
Sección titulada «Migrar el constructo UserIdentity»El constructo UserIdentity
generalmente puede intercambiarse sin cambios ajustando las importaciones.
import { UserIdentity } from "@aws/pdk/identity";import { UserIdentity } from ':shopping-list/common-constructs';...const userIdentity = new UserIdentity(this, `${id}UserIdentity`);
Nota que los constructs subyacentes usados por el nuevo UserIdentity
se obtienen directamente de aws-cdk-lib
, mientras que PDK usaba @aws-cdk/aws-cognito-identitypool-alpha
.
Migrar el constructo API
Sección titulada «Migrar el constructo API»La aplicación PDK de lista de compras tenía un constructo en constructs/apis/myapi.ts
que instanciaba un constructo CDK generado por Type Safe API desde tu modelo Smithy.
Además de este constructo, como el proyecto PDK usaba el trait @handler
, también se generaban constructs CDK de funciones lambda.
Al igual que Type Safe API, el Nx Plugin para AWS proporciona type-safety para integraciones basadas en tu modelo Smithy, pero se logra de una manera mucho más simple y flexible. En lugar de generar un constructo CDK completo en tiempo de compilación, solo se generan “metadatos” mínimos, que el archivo packages/common/constructs/src/app/apis/api.ts
usa de manera genérica. Puedes aprender más sobre cómo usar el constructo en la guía del generador ts#smithy-api
.
Sigue estos pasos:
-
Instanciar el constructo
Api
enapplication-stack.ts
stacks/application-stack.ts import { MyApi } from "../constructs/apis/myapi";import { Api } from ':shopping-list/common-constructs';...const myapi = new MyApi(this, "MyApi", {databaseConstruct,userIdentity,});const api = new Api(this, 'MyApi', {integrations: Api.defaultIntegrations(this).build(),});Nota que usamos
Api.defaultIntegrations(this).build()
- el comportamiento por defecto es crear una función lambda para cada operación en nuestra API, igual que teníamos enmyapi.ts
. -
Otorgar permisos para que las funciones lambda accedan a la tabla DynamoDB.
En la aplicación PDK,
DatabaseConstruct
se pasaba aMyApi
, y este manejaba los permisos relevantes para cada función generada. Haremos esto directamente enapplication-stack.ts
accediendo a la propiedad type-safeintegrations
del constructoApi
:stacks/application-stack.ts // Otorgar acceso limitado a Dynamo para nuestras funciones lambdadatabaseConstruct.shoppingListTable.grantReadData(api.integrations.getShoppingLists.handler,);[api.integrations.putShoppingList.handler,api.integrations.deleteShoppingList.handler,].forEach((f) => databaseConstruct.shoppingListTable.grantWriteData(f)); -
Otorgar permisos para que usuarios autenticados invoquen la API.
Dentro de
myapi.ts
de la aplicación PDK, los usuarios autenticados también tenían permisos IAM para invocar la API. Haremos el equivalente enapplication-stack.ts
:stacks/application-stack.ts api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
Migrar el constructo Website
Sección titulada «Migrar el constructo Website»Finalmente, agregamos el constructo Website
desde packages/common/constructs/src/app/static-websites/website.ts
a application-stack.ts
, ya que es el equivalente al packages/infra/src/constructs/websites/website.ts
de la aplicación PDK.
import { Website } from "../constructs/websites/website";import { Website } from ':shopping-list/common-constructs';...new Website(this, "Website", { userIdentity, myapi,});new Website(this, 'Website');
Nota que no pasamos la identidad ni la API al sitio web - la configuración en tiempo de ejecución se maneja dentro de cada constructo proporcionado por el Nx Plugin para AWS, donde UserIdentity
y Api
registran los valores necesarios, y Website
maneja su despliegue en /runtime-config.json
en tu sitio web estático.
Compilemos el proyecto ahora que hemos migrado todas las partes relevantes del código base a nuestro nuevo proyecto.
pnpm nx run-many --target build
yarn nx run-many --target build
npx nx run-many --target build
bunx nx run-many --target build
Desplegar
Sección titulada «Desplegar»Ahora que tenemos nuestra base de código completamente migrada, podemos considerar su despliegue. Existen dos caminos que podemos tomar en este punto.
Recursos Completamente Nuevos (Sencillo)
Sección titulada «Recursos Completamente Nuevos (Sencillo)»El enfoque más simple es tratar esto como una aplicación completamente nueva, lo que significa que “comenzaremos de nuevo” con una tabla DynamoDB fresca y un Grupo de Usuarios de Cognito nuevo - perdiendo todos los usuarios y sus listas de compras. Para este enfoque, simplemente:
-
Elimina la tabla DynamoDB llamada
shopping_list
-
Despliega la nueva aplicación:
Terminal window pnpm nx deploy infra shopping-list-infra-sandbox/*Terminal window yarn nx deploy infra shopping-list-infra-sandbox/*Terminal window npx nx deploy infra shopping-list-infra-sandbox/*Terminal window bunx nx deploy infra shopping-list-infra-sandbox/*
🎉 ¡Y hemos terminado! 🎉
Migrar Recursos con Estado Existentes sin Tiempo de Inactividad (Más Complejo)
Sección titulada «Migrar Recursos con Estado Existentes sin Tiempo de Inactividad (Más Complejo)»En la realidad, es más probable que quieras migrar los recursos existentes de AWS para que sean gestionados por la nueva base de código, evitando cualquier tiempo de inactividad para tus clientes.
Para nuestra aplicación de lista de compras, los recursos con estado que nos importan son la tabla DynamoDB que contiene las listas de compras de nuestros usuarios, y el Grupo de Usuarios que contiene los detalles de todos nuestros usuarios registrados. Nuestro plan de alto nivel será retener estos dos recursos clave y moverlos para que sean gestionados por nuestro nuevo stack, luego actualizar el DNS para apuntar a nuestro nuevo sitio web (y API si está expuesta a los clientes).
-
Actualiza tu nueva aplicación para hacer referencia a los recursos existentes que deseas conservar.
Para la aplicación de lista de compras, hacemos esto para la tabla DynamoDB
constructs/database.ts this.shoppingListTable = new Table(this, 'ShoppingList', {...this.shoppingListTable = Table.fromTableName(this,'ShoppingList','shopping_list',);Y para el Grupo de Usuarios de Cognito
packages/common/constructs/src/core/user-identity.ts this.userPool = this.createUserPool();this.userPool = UserPool.fromUserPoolId(this,'UserPool','<your-user-pool-id>',); -
Construye y despliega la nueva aplicación:
Terminal window pnpm nx run-many --target buildTerminal window yarn nx run-many --target buildTerminal window npx nx run-many --target buildTerminal window bunx nx run-many --target buildTerminal window pnpm nx deploy infra shopping-list-infra-sandbox/*Terminal window yarn nx deploy infra shopping-list-infra-sandbox/*Terminal window npx nx deploy infra shopping-list-infra-sandbox/*Terminal window bunx nx deploy infra shopping-list-infra-sandbox/*Ahora tenemos nuestra nueva aplicación implementada haciendo referencia a los recursos existentes, sin recibir tráfico aún.
-
Realiza pruebas de integración completas para asegurar que la nueva aplicación funciona como se espera. Para la aplicación de lista de compras, carga el sitio web y verifica que puedes iniciar sesión y crear, ver, editar y eliminar listas de compras.
-
Revierte los cambios que referencian los recursos existentes en tu nueva aplicación, pero no los despliegues aún.
constructs/database.ts this.shoppingListTable = new Table(this, 'ShoppingList', {...this.shoppingListTable = Table.fromTableName(this,'ShoppingList','shopping_list',);Y para el Grupo de Usuarios de Cognito
packages/common/constructs/src/core/user-identity.ts this.userPool = this.createUserPool();this.userPool = UserPool.fromUserPoolId(this,'UserPool','<your-user-pool-id>',);Y luego ejecuta una compilación
Terminal window pnpm nx run-many --target buildTerminal window yarn nx run-many --target buildTerminal window npx nx run-many --target buildTerminal window bunx nx run-many --target build -
Usa
cdk import
en la carpetapackages/infra
de tu nueva aplicación para ver qué recursos nos pedirá importar.New Application cd packages/infrapnpm exec cdk import shopping-list-infra-sandbox/Application --forceAvanza por las indicaciones presionando enter. La importación fallará porque los recursos están gestionados por otro stack - esto es esperado, hicimos este paso solo para confirmar qué recursos necesitaremos retener. Verás una salida como esta:
Ventana de terminal shopping-list-infra-sandbox/Application/ApplicationUserIdentity/UserPool/smsRole/Resource (AWS::IAM::Role): ingresa RoleName (vacío para omitir)shopping-list-infra-sandbox/Application/ApplicationUserIdentity/UserPool/Resource (AWS::Cognito::UserPool): ingresa UserPoolId (vacío para omitir)shopping-list-infra-sandbox/Application/Database/ShoppingList/Resource (AWS::DynamoDB::Table): importar con TableName=shopping_list (y/n) yEsto nos dice que en realidad hay 3 recursos que necesitaremos importar a nuestro nuevo stack.
-
Actualiza tu proyecto PDK antiguo para establecer
RemovalPolicy
enRETAIN
para los recursos descubiertos en el paso anterior. Al momento de escribir esto, es el valor predeterminado tanto para el Grupo de Usuarios como para la tabla DynamoDB, pero necesitamos actualizarlo para el Rol SMS que descubrimos arriba:application-stack.ts const userIdentity = new UserIdentity(this, `${id}UserIdentity`, {userPool,});const smsRole = userIdentity.userPool.node.findAll().filter(c => CfnResource.isCfnResource(c) &&c.node.path.includes('/smsRole/'))[0] as CfnResource;smsRole.applyRemovalPolicy(RemovalPolicy.RETAIN); -
Despliega tu proyecto PDK para que las políticas de eliminación se apliquen
PDK Application cd packages/infranpx projen deploy -
Revisa la consola de CloudFormation y registra los valores que te solicitaron en el paso anterior de
cdk import
- El ID del Grupo de Usuarios, ej.
us-west-2_XXXXX
- El Nombre del Rol SMS, ej.
infra-sandbox-UserIdentityUserPoolsmsRoleXXXXXX
- El ID del Grupo de Usuarios, ej.
-
Actualiza tu proyecto PDK para hacer referencia a los recursos existentes en lugar de crearlos
constructs/database.ts this.shoppingListTable = new Table(this, 'ShoppingList', {...this.shoppingListTable = Table.fromTableName(this,'ShoppingList','shopping_list',);Y para el Grupo de Usuarios de Cognito
application-stack.ts const userPool = UserPool.fromUserPoolId(this,'UserPool','<your-user-pool-id>',);const userIdentity = new UserIdentity(this, `${id}UserIdentity`, {// El constructo PDK acepta UserPool no IUserPool, ¡pero esto aún funciona!userPool: userPool as any,}); -
Despliega tu proyecto PDK nuevamente, esto significará que los recursos ya no son gestionados por el stack de CloudFormación de tu proyecto PDK.
PDK Application cd packages/infranpx projen deploy -
Ahora que los recursos no están gestionados, podemos ejecutar
cdk import
en nuestra nueva aplicación para realizar la importación realmente:New Application cd packages/infrapnpm exec cdk import shopping-list-infra-sandbox/Application --forceIngresa los valores cuando se soliciten, la importación debería completarse exitosamente.
-
Despliega la nueva aplicación nuevamente para asegurar que cualquier cambio en estos recursos existentes (ahora gestionados por tu nuevo stack) se realice:
Terminal window pnpm nx deploy infra shopping-list-infra-sandbox/*Terminal window yarn nx deploy infra shopping-list-infra-sandbox/*Terminal window npx nx deploy infra shopping-list-infra-sandbox/*Terminal window bunx nx deploy infra shopping-list-infra-sandbox/* -
Realiza una prueba completa de tu nueva aplicación nuevamente
-
Actualiza los registros DNS para apuntar a tu nuevo Sitio web (y API si es requerido).
Recomendamos un enfoque gradual usando Enrutamiento Ponderado de Route53, donde una fracción de las solicitudes se dirigen a la nueva aplicación inicialmente. Mientras monitoreas tus métricas, puedes incrementar el peso para la nueva aplicación hasta que no se envíe tráfico a tu antigua aplicación PDK.
Si no tienes DNS y usaste los dominios auto-generados para el sitio web y API, siempre puedes considerar proxy de solicitudes (ej. vía un origen HTTP de CloudFront o integración(es) HTTP de API Gateway).
-
Monitorea las métricas de la aplicación PDK para asegurar que no hay tráfico, y finalmente destruye el stack de CloudFormation antiguo:
Ventana de terminal cd packages/infranpx projen destroy
¡Esto fue bastante más involucrado, pero migramos exitosamente a nuestros usuarios sin problemas a la nueva aplicación! 🎉🎉🎉
Ahora tenemos los nuevos beneficios del Plugin Nx para AWS sobre PDK:
- Compilaciones más rápidas
- Soporte para desarrollo local de API
- Una base de código amigable para el coding de ambiente (¡prueba nuestro servidor MCP!)
- Código cliente/servidor con tipos más intuitivo
- ¡Y más!
Preguntas Frecuentes
Sección titulada «Preguntas Frecuentes»Esta sección proporciona orientación para características de PDK que no están cubiertas por el ejemplo de migración anterior.
Como regla general al migrar desde PDK, recomendamos comenzar cualquier proyecto con un Espacio de Trabajo Nx, dada su similitud con el Monorepo de PDK. También recomendamos usar nuestros generadores como primitivas para construir nuevos tipos.
npx create-nx-workspace@21.4.1 my-project --pm=pnpm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@21.4.1 my-project --pm=yarn --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@21.4.1 my-project --pm=npm --preset=@aws/nx-plugin --ci=skip
npx create-nx-workspace@21.4.1 my-project --pm=bun --preset=@aws/nx-plugin --ci=skip
CDK Graph
Sección titulada «CDK Graph»CDK Graph construye gráficos de tus recursos CDK conectados y ofrece dos plugins:
Diagram Plugin
Sección titulada «Diagram Plugin»El CDK Graph Diagram Plugin genera diagramas de arquitectura de AWS a partir de tu infraestructura CDK.
Para un enfoque determinista similar, una alternativa viable es CDK-Dia.
Con los avances en IA Generativa, muchos modelos fundacionales pueden crear diagramas de alta calidad a partir de tu infraestructura CDK. Recomendamos probar el AWS Diagram MCP Server. Consulta esta entrada de blog para ver un tutorial paso a paso.
Threat Composer Plugin
Sección titulada «Threat Composer Plugin»El CDK Graph Threat Composer Plugin genera un modelo de amenazas inicial para Threat Composer a partir de tu código CDK.
Este plugin funcionaba simplemente filtrando un modelo de amenazas base que contenía ejemplos de amenazas, y filtrándolas según los recursos utilizados por tu stack.
Si te interesan estos ejemplos específicos de amenazas, puedes copiar y filtrar el modelo base, o usarlo como contexto para que un modelo fundacional genere uno similar.
AWS Arch
Sección titulada «AWS Arch»AWS Arch proporciona mapeos entre recursos de CloudFormation y sus iconos de arquitectura asociados para CDK Graph.
Consulta la página de iconos de arquitectura de AWS para recursos relacionados con iconos. Diagrams también ofrece una forma de construir diagramas como código.
Si estabas usando esto directamente, ¡considera hacer un fork del proyecto y tomar posesión del mismo!
Pipeline
Sección titulada «Pipeline»El PDK proporcionaba un PDKPipelineProject
que configuraba un proyecto de infraestructura CDK y utilizaba un constructo CDK que encapsulaba algunos recursos de CDK Pipelines.
Para migrar desde esto, puedes usar directamente los constructos de CDK Pipelines. Sin embargo, en la práctica suele ser más sencillo usar soluciones como GitHub Actions o GitLab CI/CD, donde defines CDK Stages y ejecutas el comando de despliegue para la etapa correspondiente directamente.
PDK Nag
Sección titulada «PDK Nag»PDK Nag envuelve CDK Nag y proporciona un conjunto de reglas específicas para la creación de prototipos.
Para migrar desde PDK Nag, utiliza CDK Nag directamente. Si necesitas el mismo conjunto de reglas, puedes crear tu propio “pack” siguiendo la documentación aquí.
Type Safe API
Sección titulada «Type Safe API»Los componentes más utilizados de Type Safe API están cubiertos en el ejemplo de migración anterior, sin embargo, hay otras características cuyos detalles de migración se describen a continuación.
APIs modeladas con OpenAPI
Sección titulada «APIs modeladas con OpenAPI»El Plugin de Nx para AWS admite APIs modeladas en Smithy, pero no aquellas modeladas directamente en OpenAPI. El generador ts#smithy-api
es un buen punto de partida que luego puedes modificar. Puedes definir tu especificación OpenAPI en la carpeta src
del proyecto model
en lugar de Smithy, y modificar el build.Dockerfile
para usar tu herramienta de generación de código preferida para clientes/servidores si no están disponibles en NPM. Si tus herramientas deseadas están en NPM, puedes instalarlas como dependencias de desarrollo en tu espacio de trabajo Nx y llamarlas directamente como objetivos de compilación de Nx.
Backend
Sección titulada «Backend»Para backends type-safe modelados en OpenAPI, puedes considerar usar uno de los Generadores de Servidor de OpenAPI Generator. Estos no generan directamente para AWS Lambda, pero puedes usar el AWS Lambda Web Adapter como puente para muchos de ellos.
Cliente
Sección titulada «Cliente»Para clientes TypeScript, puedes usar el generador ts#react-website
y el generador api-connection
con un ejemplo de ts#smithy-api
para ver cómo se generan e integran los clientes con un sitio web. Esto configura objetivos de compilación que generan clientes invocando nuestros generadores open-api#ts-client
o open-api#ts-hooks
. Puedes usar estos generadores tú mismo apuntándolos a tu Especificación OpenAPI.
Para otros lenguajes, también puedes ver si alguno de los generadores de OpenAPI Generator se ajusta a tus necesidades.
También puedes construir un generador personalizado usando el generador ts#nx-generator
. Consulta la documentación de ese generador para detalles sobre cómo generar código desde OpenAPI. Puedes usar las plantillas del Plugin de Nx para AWS como punto de partida. Incluso puedes consultar las plantillas del código base de PDK para más inspiración, notando que la estructura de datos sobre la que operan las plantillas es un poco diferente a la del Plugin de Nx para AWS.
APIs modeladas con TypeSpec
Sección titulada «APIs modeladas con TypeSpec»Para TypeSpec, aplica también la sección anterior sobre OpenAPI. Puedes comenzar generando un ts#smithy-api
, instalar el compilador de TypeSpec y los paquetes de OpenAPI en tu espacio de trabajo Nx, y actualizar el objetivo compile
del proyecto model
para ejecutar tsp compile
en su lugar, asegurando que genere una especificación OpenAPI en el directorio dist
.
Backend
Sección titulada «Backend»El enfoque recomendado sería usar el generador de servidor HTTP TypeSpec para JavaScript para generar tu código de servidor, ya que funciona directamente sobre tu modelo TypeSpec.
Puedes usar el AWS Lambda Web Adapter para ejecutar el servidor generado en AWS Lambda.
También puedes usar cualquiera de las opciones de OpenAPI mencionadas anteriormente.
Cliente
Sección titulada «Cliente»TypeSpec tiene sus propios generadores de código para clientes en los tres lenguajes soportados por Type Safe API:
La sección anterior sobre OpenAPI también aplica ya que TypeSpec puede compilar a OpenAPI.
APIs modeladas con Smithy
Sección titulada «APIs modeladas con Smithy»El ejemplo de migración anterior describe la migración para usar el generador ts#smithy-api
. Esta sección cubre las opciones para backends y clientes en Python y Java.
Backend
Sección titulada «Backend»El generador de código Smithy para Java. Este incluye un generador de servidor Java así como un adaptador para ejecutar el servidor Java generado en AWS Lambda.
Smithy no tiene un generador de servidor para Python, por lo que necesitarás usar OpenAPI. Consulta la sección anterior sobre APIs modeladas con OpenAPI para opciones potenciales.
Cliente
Sección titulada «Cliente»El generador de código Smithy para Java. Este incluye un generador de cliente Java.
Para clientes Python, puedes revisar Smithy Python.
Para TypeScript, consulta Smithy TypeScript, o usa el mismo enfoque que hemos tomado en ts#smithy-api
mediante OpenAPI (optamos por esto para tener consistencia entre APIs tRPC, FastAPI y Smithy mediante hooks de TanStack Query).
Biblioteca de formas Smithy
Sección titulada «Biblioteca de formas Smithy»Type Safe API proporcionaba un tipo de proyecto Projen llamado SmithyShapeLibraryProject
que configuraba un proyecto con modelos Smithy reutilizables por múltiples APIs basadas en Smithy.
La forma más directa de lograr esto es:
Crear una biblioteca de formas
Sección titulada «Crear una biblioteca de formas»-
Crea tu biblioteca de formas usando el generador
smithy#project
:- Instale el Nx Console VSCode Plugin si aún no lo ha hecho
- Abra la consola Nx en VSCode
- Haga clic en
Generate (UI)
en la sección "Common Nx Commands" - Busque
@aws/nx-plugin - smithy#project
- Complete los parámetros requeridos
- Haga clic en
Generate
Terminal window pnpm nx g @aws/nx-plugin:smithy#projectTerminal window yarn nx g @aws/nx-plugin:smithy#projectTerminal window npx nx g @aws/nx-plugin:smithy#projectTerminal window bunx nx g @aws/nx-plugin:smithy#projectTambién puede realizar una ejecución en seco para ver qué archivos se cambiarían
Terminal window pnpm nx g @aws/nx-plugin:smithy#project --dry-runTerminal window yarn nx g @aws/nx-plugin:smithy#project --dry-runTerminal window npx nx g @aws/nx-plugin:smithy#project --dry-runTerminal window bunx nx g @aws/nx-plugin:smithy#project --dry-runEspecifica cualquier nombre para la opción
serviceName
, ya que eliminaremos la formaservice
. -
Reemplaza el modelo predeterminado en
src
con las formas que deseas definir -
Actualiza
smithy-build.json
para eliminar losplugins
y cualquier dependencia Maven no utilizada -
Reemplaza
build.Dockerfile
con pasos de compilación mínimos:build.Dockerfile FROM public.ecr.aws/docker/library/node:24 AS builder# Output directoryRUN mkdir /out# Install Smithy CLI# https://smithy.io/2.0/guides/smithy-cli/cli_installation.htmlWORKDIR /smithyARG TARGETPLATFORMRUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then ARCH="aarch64"; else ARCH="x86_64"; fi && \mkdir -p smithy-install/smithy && \curl -L https://github.com/smithy-lang/smithy/releases/download/1.61.0/smithy-cli-linux-$ARCH.zip -o smithy-install/smithy-cli-linux-$ARCH.zip && \unzip -qo smithy-install/smithy-cli-linux-$ARCH.zip -d smithy-install && \mv smithy-install/smithy-cli-linux-$ARCH/* smithy-install/smithyRUN smithy-install/smithy/install# Copy project filesCOPY smithy-build.json .COPY src src# Smithy build with Maven cache mountRUN --mount=type=cache,target=/root/.m2/repository,id=maven-cache \smithy buildRUN cp -r build/* /out/# Export the /out directoryFROM scratch AS exportCOPY --from=builder /out /
Consumir la biblioteca de formas
Sección titulada «Consumir la biblioteca de formas»En tus proyectos de modelo de servicio, realiza los siguientes cambios para consumir la biblioteca de formas:
-
Actualiza el objetivo
compile
enproject.json
para agregar el contexto de compilación del workspace y una dependencia en el objetivobuild
de la biblioteca de formasproject.json {"cache": true,"outputs": ["{workspaceRoot}/dist/{projectRoot}/build"],"executor": "nx:run-commands","options": {"commands": ["rimraf dist/packages/api/model/build","make-dir dist/packages/api/model/build","docker build --build-context workspace=. -f packages/api/model/build.Dockerfile --target export --output type=local,dest=dist/packages/api/model/build packages/api/model"],"parallel": false,"cwd": "{workspaceRoot}"},"dependsOn": ["@my-project/shapes:build"]} -
Actualiza el
build.Dockerfile
para copiar el directoriosrc
de tu biblioteca de formas. Por ejemplo, asumiendo que la biblioteca está enpackages/shapes
:build.Dockerfile # Copy project filesCOPY smithy-build.json .COPY src srcCOPY --from=workspace packages/shapes/src shapes -
Actualiza
smithy-build.json
para agregar el directorio de formas a sussources
:smithy-build.json {"version": "1.0","sources": ["src/", "shapes/"],"plugins": {...}
Interceptores
Sección titulada «Interceptores»Type Safe API proporcionaba los siguientes interceptores predeterminados:
- Interceptores de logging, tracing y métricas usando Powertools para AWS Lambda
- Interceptor try-catch para manejar excepciones no capturadas
- Interceptor CORS para devolver encabezados CORS
El generador ts#smithy-api
instrumenta logging, tracing y métricas con Powertools para AWS Lambda usando Middy. El comportamiento del interceptor try-catch está integrado en Smithy TypeScript SSDK, y los encabezados CORS se agregan en handler.ts
.
Para interceptores de logging, tracing y métricas en cualquier lenguaje, usa Powertools para AWS Lambda directamente.
Para migrar interceptores personalizados, recomendamos usar las siguientes bibliotecas:
- TypeScript - Middy
- Python - Powertools para AWS Lambda Middleware Factory
- Java - Instrumenta métodos antes/después de tu lógica de negocio usando aws-lambda-java-libs para un enfoque simple, o considera AspectJ para construir middleware como anotaciones.
Generación de documentación
Sección titulada «Generación de documentación»Type Safe API proporcionaba generación de documentación usando Redocly CLI. Esto es muy fácil de agregar a un proyecto existente una vez migrado.
-
Instala Redocly CLI
Terminal window pnpm add -Dw @redocly/cliTerminal window yarn add -D @redocly/cliTerminal window npm install --legacy-peer-deps -D @redocly/cliTerminal window bun install -D @redocly/cli -
Agrega un objetivo de generación de documentación a tu proyecto
model
usandoredocly build-docs
, por ejemplo:model/project.json {..."documentation": {"cache": true,"outputs": ["{workspaceRoot}/dist/{projectRoot}/documentation"],"executor": "nx:run-commands","options": {"command": "redocly build-docs dist/packages/api/model/build/openapi/openapi.json --output=dist/packages/api/model/documentation/index.html","cwd": "{workspaceRoot}"},"dependsOn": ["compile"]}}
También puedes considerar los generadores de documentación de OpenAPI Generator.
Integraciones simuladas
Sección titulada «Integraciones simuladas»Type Safe API generaba simulaciones para ti dentro de su paquete de infraestructura generado.
Puedes migrar a JSON Schema Faker que puede crear datos simulados basados en JSON Schemas. Esto puede funcionar directamente en una especificación OpenAPI, y tiene una CLI que podrías ejecutar como parte de la compilación de tu proyecto model
.
Puedes actualizar tu infraestructura CDK para leer el archivo JSON generado por JSON Schema Faker y devolver la MockIntegration
apropiada de API Gateway para una integración, basada en el metadata.gen.ts
generado (asumiendo que usaste el generador ts#smithy-api
).
Backends en múltiples lenguajes
Sección titulada «Backends en múltiples lenguajes»Type Safe API admitía implementar APIs con una mezcla de diferentes lenguajes en el backend. Esto también se puede lograr proporcionando “overrides” a las integraciones al instanciar tu constructo de API en CDK:
const pythonLambdaHandler = new Function(this, 'PythonImplementation', { runtime: Runtime.PYTHON_3_12, ...});
new MyApi(this, 'MyApi', { integrations: Api.defaultIntegrations(this) .withOverrides({ echo: { integration: new LambdaIntegration(pythonLambdaHandler), handler: pythonLambdaHandler, }, }) .build(),});
Necesitarás “stubear” tu servicio/router para que tu servicio se compile si usas ts#smithy-api
y el SDK de servidor TypeScript, ej:
export const Service: ApiService<ServiceContext> = { ... Echo: () => { throw new Error(`Not Implemented`); },};
Validación de entrada
Sección titulada «Validación de entrada»Type Safe API agregaba validación nativa de API Gateway para cuerpos de solicitud basada en tu especificación OpenAPI, ya que usaba el constructo SpecRestApi
internamente.
Con el generador ts#smithy-api
, la validación la realiza el propio SDK del servidor. Esto es igual para la mayoría de generadores de servidores.
Si deseas implementar validación nativa de API Gateway, podrías hacerlo modificando packages/common/constructs/src/core/api/rest-api.ts
para leer el JSON schema relevante del cuerpo de solicitud de cada operación desde tu especificación OpenAPI.
APIs WebSocket
Sección titulada «APIs WebSocket»Desafortunadamente no hay una ruta de migración directa para las APIs WebSocket de Type Safe API usando API Gateway y Lambda con desarrollo de API basado en modelos. Sin embargo, esta sección del guía busca al menos ofrecer algunas ideas.
Considera usar AsyncAPI para modelar tu API en lugar de OpenAPI o TypeSpec, ya que está diseñado para APIs asíncronas. La Plantilla NodeJS de AsyncAPI puede generar un backend WebSocket Node que podrías alojar en ECS, por ejemplo.
También puedes considerar AppSync Events para infraestructura, y usar Powertools. Este blog post vale la pena leerlo.
Otra opción es usar APIs GraphQL con WebSocket en AppSync, para lo cual tenemos un issue de GitHub que puedes +1. Consulta la guía para desarrolladores de AppSync para detalles y enlaces a proyectos de ejemplo.
También puedes considerar crear tus propios generadores de código que interpreten las mismas extensiones de vendor que Type Safe API. Consulta la sección APIs modeladas con OpenAPI para detalles sobre cómo construir generadores de código personalizados basados en OpenAPI. Puedes encontrar las plantillas que Type Safe API usa para los manejadores Lambda de API Gateway WebSocket aquí, y el cliente aquí.
También puedes considerar migrar al generador ts#trpc-api
para usar tRPC. Al momento de escribir esto, aún no tenemos soporte para suscripciones/streaming, pero si necesitas esto, agrega un +1 a nuestro issue de GitHub.
Smithy es agnóstico al protocolo, pero aún no tiene soporte para el protocolo WebSocket, consulta este issue de GitHub.
Infraestructura en Python o Java
Sección titulada «Infraestructura en Python o Java»El PDK admite infraestructura CDK escrita en Python y Java. Actualmente, esto no está soportado en el Plugin de Nx para AWS al momento de escribir este documento.
La ruta recomendada sería migrar tu infraestructura CDK a TypeScript, o usar nuestros generadores y migrar el paquete de construcciones comunes a tu lenguaje deseado. Puedes usar IA Generativa para acelerar este tipo de migraciones, por ejemplo Amazon Q CLI. Un agente de IA puede iterar en la migración hasta que las plantillas sintetizadas de CloudFormation sean idénticas.
Lo mismo aplica para la infraestructura generada por Type Safe API en Python o Java - puedes traducir el constructo genérico rest-api.ts
del paquete de construcciones comunes, e implementar tu propio generador de metadatos simple para el lenguaje objetivo (consulta la sección APIs Modelled with OpenAPI).
Puedes usar el generador py#project
como base para proyectos Python donde agregar tu código CDK (y migrar tu archivo cdk.json
, añadiendo targets relevantes). Para proyectos Java, puedes usar el plugin @nx/gradle
de Nx, o @jnxplus/nx-maven
para Maven.
Uso de Projen
Sección titulada «Uso de Projen»El PDK fue construido sobre Projen. Projen y los generadores de Nx tienen diferencias bastante fundamentales, lo que significa que, aunque técnicamente es posible combinarlos, probablemente sea un antipatrón. Projen gestiona los archivos del proyecto como código de tal manera que no se pueden modificar directamente, mientras que los generadores de Nx generan los archivos del proyecto una vez y luego el código se puede modificar libremente.
Si deseas continuar usando Projen, puedes implementar tus tipos de proyectos Projen deseados por tu cuenta. Para seguir los patrones del Nx Plugin para AWS, puedes ejecutar nuestros generadores o examinar su código fuente en GitHub para ver cómo se construyen los tipos de proyectos que necesitas, e implementar las partes relevantes usando las primitivas de Projen.