Migrando do AWS PDK
Este guia orienta você através de uma migração exemplo de um projeto AWS PDK para o Nx Plugin for AWS, além de fornecer orientações gerais sobre o tema.
Migrar para o Nx Plugin for AWS oferece os seguintes benefícios em relação ao PDK:
- Compilações mais rápidas
- Mais fácil de usar (UI e CLI)
- Compatível com vibe-coding (experimente nosso servidor MCP!)
- Tecnologias mais modernas
- Desenvolvimento local de APIs e websites
- Mais controle (modifique arquivos fornecidos para seu caso de uso)
- E muito mais!
Exemplo de Migração: Aplicação Lista de Compras
Seção intitulada “Exemplo de Migração: Aplicação Lista de Compras”Neste guia, usaremos a Aplicação Lista de Compras do Tutorial PDK como nosso projeto alvo para migração. Siga as etapas daquele tutorial para criar o projeto se quiser acompanhar.
A aplicação lista de compras consiste nos seguintes tipos de projeto PDK:
MonorepoTsProject
TypeSafeApiProject
CloudscapeReactTsWebsiteProject
InfrastructureTsProject
Criar Workspace
Seção intitulada “Criar Workspace”Começaremos criando um novo workspace para nosso projeto. Embora mais radical que uma migração in-place, esta abordagem fornece o resultado mais limpo. Criar um workspace Nx é equivalente ao MonorepoTsProject
do 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
Abra o diretório shopping-list
criado em seu IDE favorito.
Migrar a API
Seção intitulada “Migrar a API”O TypeSafeApiProject
utilizado na aplicação de lista de compras fez uso de:
- Smithy como linguagem de modelagem
- TypeScript para implementação de operações
- Geração de hooks TypeScript para integração com um site React
Portanto, podemos utilizar o gerador ts#smithy-api
para fornecer funcionalidade equivalente.
Gerar uma API Smithy em TypeScript
Seção intitulada “Gerar uma API Smithy em TypeScript”Execute o gerador ts#smithy-api
para configurar seu projeto de API em packages/api
:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#smithy-api
- Preencha os parâmetros obrigatórios
- name: api
- namespace: com.aws
- auth: IAM
- Clique em
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
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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
Você notará que isso gera um projeto model
, além de um projeto backend
. O projeto model
contém seu modelo Smithy, e backend
contém a implementação do servidor.
O backend utiliza o Smithy Server Generator for TypeScript. Exploraremos isso mais adiante.
Migrar o Modelo Smithy
Seção intitulada “Migrar o Modelo Smithy”Agora que temos a estrutura básica para nosso projeto Smithy API, podemos migrar o modelo:
-
Exclua os arquivos Smithy de exemplo gerados em
packages/api/model/src
-
Copie seu modelo do diretório
packages/api/model/src/main/smithy
do projeto PDK para o diretóriopackages/api/model/src
do novo projeto. -
Atualize o nome do serviço e namespace em
smithy-build.json
para corresponder à aplicação PDK:smithy-build.json "plugins": {"openapi": {"service": "com.aws#MyApi",... -
Atualize o serviço em
main.smithy
para adicionar o erroValidationException
, necessário ao usar o Smithy TypeScript Server SDK.main.smithy use smithy.framework#ValidationException/// My Shopping List API@restJson1service MyApi {version: "1.0"operations: [GetShoppingListsPutShoppingListDeleteShoppingList]errors: [BadRequestErrorNotAuthorizedErrorInternalFailureErrorValidationException]} -
Adicione um arquivo
extensions.smithy
empackages/api/model/src
onde definiremos um trait que fornece informações de paginação para o cliente gerado:extensions.smithy $version: "2"namespace com.awsuse smithy.openapi#specificationExtension@trait@specificationExtension(as: "x-cursor")structure cursor {inputToken: Stringenabled: Boolean} -
Adicione o novo trait
@cursor
à operaçãoGetShoppingLists
emget-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}Qualquer operação
@paginated
também deve usar@cursor
se você estiver usando o gerador de cliente fornecido pelo Nx Plugin for AWS (via o geradorapi-connection
). -
Finalmente, remova o trait
@handler
de todas as operações, pois não é suportado pelo Nx Plugin for AWS. Usandots#smithy-api
, não precisamos das constructs CDK de funções lambda geradas automaticamente e alvos de bundling gerados por este trait, pois usamos um único bundle para todas as funções lambda.
Neste ponto, vamos executar um build para verificar nossas alterações no modelo e garantir que temos algum código de servidor gerado para trabalhar. Haverá algumas falhas no projeto backend (@shopping-list/api
), mas abordaremos isso a seguir.
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 os Lambda Handlers
Seção intitulada “Migrar os Lambda Handlers”Você pode considerar o projeto api/backend
como equivalente ao projeto api/handlers/typescript
do Type Safe API.
Uma das principais diferenças entre o Type Safe API e o gerador ts#smithy-api
é que os handlers são implementados usando o Smithy Server Generator for TypeScript, em vez dos wrappers de handler gerados pelo Type Safe API (encontrados no projeto api/generated/typescript/runtime
).
Os lambda handlers da aplicação de lista de compras dependem do pacote @aws-sdk/client-dynamodb
, então vamos instalá-lo primeiro:
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
Em seguida, vamos copiar o arquivo handlers/src/dynamo-client.ts
do projeto PDK para backend/src/operations
para que esteja disponível para nossos handlers.
Para migrar os handlers, você pode seguir estes passos gerais:
-
Copie o handler do diretório
packages/api/handlers/typescript/src
do seu projeto PDK para o diretóriopackages/api/backend/src/operations
do novo projeto. -
Remova as importações de
my-api-typescript-runtime
e importe o tipo de operação do TypeScript Server SDK gerado, bem como oServiceContext
por exemplo:import {deleteShoppingListHandler,DeleteShoppingListChainedHandlerFunction,INTERCEPTORS,Response,LoggingInterceptor,} from 'myapi-typescript-runtime';import { DeleteShoppingList as DeleteShoppingListOperation } from '../generated/ssdk/index.js';import { ServiceContext } from '../context.js'; -
Exclua a exportação do wrapper do handler
export const handler = deleteShoppingListHandler(...INTERCEPTORS,deleteShoppingList,); -
Atualize a assinatura do seu handler de operação para usar o SSDK:
export const deleteShoppingList: DeleteShoppingListChainedHandlerFunction = async (request) => {export const DeleteShoppingList: DeleteShoppingListOperation<ServiceContext> = async (input, ctx) => { -
Substitua o uso do
LoggingInterceptor
porctx.logger
. (Também se aplica a interceptores de métricas e tracing):LoggingInterceptor.getLogger(request).info('...');ctx.logger.info('...'); -
Atualize referências aos parâmetros de entrada. Como o SSDK fornece tipos que correspondem exatamente ao seu modelo Smithy (em vez de agrupar parâmetros de path/query/header separadamente do parâmetro body), atualize quaisquer referências de entrada conforme necessário:
const shoppingListId = request.input.requestParameters.shoppingListId;const shoppingListId = input.shoppingListId; -
Remova o uso de
Response
. Em vez disso, retornamos objetos simples no SSDK.return Response.success({ shoppingListId });return { shoppingListId };Também não lançamos ou retornamos mais
Response
, em vez disso lançamos os erros gerados pelo 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' }); -
Atualize quaisquer importações para usar sintaxe ESM, ou seja, adicionando a extensão
.js
às importações relativas. -
Adicione a operação em
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';// Registre as operações no serviço aquiexport const Service: MyApiService<ServiceContext> = {PutShoppingList,GetShoppingLists,DeleteShoppingList,};
Migração do Handler da Lista de Compras
Excluir 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';
/** * Type-safe handler for the DeleteShoppingList operation */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, });};
/** * Entry point for the AWS Lambda handler for the DeleteShoppingList operation. * The deleteShoppingListHandler method wraps the type-safe handler and manages marshalling inputs and outputs */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';
/** * Type-safe handler for the DeleteShoppingList operation */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, };};
Obter 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';
/** * Type-safe handler for the GetShoppingLists operation */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, });};
/** * Decode a stringified token * @param token a token passed to the paginated request */const fromToken = <T>(token?: string): T | undefined => token ? (JSON.parse(Buffer.from(decodeURIComponent(token), 'base64').toString()) as T) : undefined;
/** * Encode pagination details into an opaque stringified token * @param paginationToken pagination token details */const toToken = <T>(paginationToken?: T): string | undefined => paginationToken ? encodeURIComponent(Buffer.from(JSON.stringify(paginationToken)).toString('base64')) : undefined;
/** * Entry point for the AWS Lambda handler for the GetShoppingLists operation. * The getShoppingListsHandler method wraps the type-safe handler and manages marshalling inputs and outputs */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';
/** * Type-safe handler for the GetShoppingLists operation */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, };};
/** * Decode a stringified token * @param token a token passed to the paginated request */const fromToken = <T>(token?: string): T | undefined => token ? (JSON.parse(Buffer.from(decodeURIComponent(token), 'base64').toString()) as T) : undefined;
/** * Encode pagination details into an opaque stringified token * @param paginationToken pagination token details */const toToken = <T>(paginationToken?: T): string | undefined => paginationToken ? encodeURIComponent(Buffer.from(JSON.stringify(paginationToken)).toString('base64')) : undefined;
Adicionar 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';
/** * Type-safe handler for the PutShoppingList operation */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, });};
/** * Entry point for the AWS Lambda handler for the PutShoppingList operation. * The putShoppingListHandler method wraps the type-safe handler and manages marshalling inputs and outputs */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';
/** * Type-safe handler for the PutShoppingList operation */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, };};
Geramos o projeto Smithy API com o nome api
inicialmente para que fosse adicionado a packages/api
para consistência com o projeto PDK. Como nossa Smithy API agora define service MyApi
em vez de service Api
, precisamos atualizar quaisquer instâncias de getApiServiceHandler
para getMyApiServiceHandler
.
Faça esta alteração em 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);
E em 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);
Além disso, atualize packages/api/backend/project.json
e atualize metadata.apiName
para my-api
:
"metadata": { "generator": "ts#smithy-api", "apiName": "api", "apiName": "my-api", "auth": "IAM", "modelProject": "@shopping-list/api-model", "ports": [3001] },
Verificar com um Build
Seção intitulada “Verificar com um Build”Agora podemos construir o projeto para verificar se a migração funcionou até agora:
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 o Website
Seção intitulada “Migrar o Website”O CloudscapeReactTsWebsiteProject
usado na aplicação de lista de compras configurava um website React com CloudScape e autenticação Cognito integrada.
Este tipo de projeto utilizava create-react-app
, que está agora depreciado. Para migrar o website neste guia, usaremos o gerador ts#react-website
, que utiliza tecnologias mais modernas e suportadas, como Vite.
Como parte da migração, também substituiremos o React Router configurado pelo PDK por TanStack Router, que adiciona maior segurança de tipos no roteamento do website.
Gerar um Website React
Seção intitulada “Gerar um Website React”Execute o gerador ts#react-website
para configurar seu projeto de website em packages/website
:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#react-website
- Preencha os parâmetros obrigatórios
- name: website
- Clique em
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
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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
Adicionar Autenticação Cognito
Seção intitulada “Adicionar Autenticação Cognito”O gerador de website React acima não inclui autenticação Cognito por padrão como o CloudscapeReactTsWebsiteProject
. Em vez disso, isso é adicionado explicitamente via o gerador ts#react-website#auth
.
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#react-website#auth
- Preencha os parâmetros obrigatórios
- project: website
- cognitoDomain: shopping-list
- Clique em
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
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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
Isso adiciona componentes React que gerenciam os redirecionamentos para garantir que os usuários façam login usando a UI hospedada do Cognito. Também adiciona um construct CDK para implantar os recursos do Cognito em packages/common/constructs
, chamado UserIdentity
.
Conectar o Website à API
Seção intitulada “Conectar o Website à API”No PDK era possível passar projetos Projen configurados entre si para gerar código de integração. Isso foi usado na aplicação de lista de compras para configurar a integração do website com a API.
Com o Nx Plugin para AWS, a integração de API é suportada via o gerador api-connection
. A seguir, usamos este gerador para permitir que nosso website invoque nossa API Smithy:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - api-connection
- Preencha os parâmetros obrigatórios
- sourceProject: website
- targetProject: api
- Clique em
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
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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
Isso gera os provedores de cliente e targets de build necessários para seu website chamar a API via um cliente TypeScript gerado.
Adicionar Dependência do AWS Northstar
Seção intitulada “Adicionar Dependência do AWS Northstar”O CloudscapeReactTsWebsiteProject
incluía automaticamente a dependência do @aws-northstar/ui
usada em nossa aplicação de lista de compras, então a adicionamos aqui:
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 e Páginas
Seção intitulada “Mover Componentes e Páginas”A aplicação de lista de compras tem um componente chamado CreateItem
e duas páginas, ShoppingList
e ShoppingLists
. Migraremos estes para o novo website, com ajustes necessários devido ao uso do TanStack Router e do gerador de cliente TypeScript do Nx Plugin para AWS.
-
Copie
packages/website/src/components/CreateItem/index.tsx
do projeto PDK para o mesmo local no novo projeto. -
Copie
packages/website/src/pages/ShoppingLists/index.tsx
parapackages/website/src/routes/index.tsx
, já queShoppingLists
é nossa página inicial e usamos roteamento baseado em arquivo com TanStack Router. -
Copie
packages/website/src/pages/ShoppingList/index.tsx
parapackages/website/src/routes/$shoppingListId.tsx
, poisShoppingList
é a página exibida na rota/:shoppingListId
.
Nota: agora você verá erros de compilação na IDE. Faremos mais ajustes para adaptar ao novo framework, conforme detalhado abaixo.
Migrar de React Router para TanStack Router
Seção intitulada “Migrar de React Router para TanStack Router”Como estamos usando roteamento baseado em arquivo, podemos usar o servidor de desenvolvimento local para gerenciar a configuração de rotas. Inicie o servidor local:
pnpm nx serve-local website
yarn nx serve-local website
npx nx serve-local website
bunx nx serve-local website
Você verá alguns erros, mas o servidor local do website deve iniciar na porta 4200
, e o servidor Smithy local na porta 3001
.
Siga os passos abaixo em ambos routes/index.tsx
e routes/$shoppingListId.tsx
para migrar para TanStack Router:
-
Adicione
createFileRoute
para registrar cada rota: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,});Após salvar, os erros de tipo com
createFileRoute
devem desaparecer. -
Substitua o hook
useNavigate
.Atualize a importação:
import { useNavigate } from 'react-router-dom';import { useNavigate } from '@tanstack/react-router';Atualize as chamadas ao método
navigate
para usar rotas tipadas:navigate(`/${cell.shoppingListId}`);navigate({to: '/$shoppingListId',params: { shoppingListId: cell.shoppingListId },}); -
Substitua o hook
useParams
.Remova a importação:
import { useParams } from 'react-router-dom';Atualize as chamadas para usar o hook da
Route
criada. Agora são tipadas!const { shoppingListId } = useParams();const { shoppingListId } = Route.useParams();
Corrigir Importações de Componentes
Seção intitulada “Corrigir Importações de Componentes”Como nossas rotas estão em locais diferentes no novo projeto, precisamos ajustar a importação de CreateItem
em ambos os arquivos de rota:
import CreateItem from "../../components/CreateItem";import CreateItem from "../components/CreateItem";
O AppLayoutContext
também está em local diferente:
import { AppLayoutContext } from "../../layouts/App";import { AppLayoutContext } from "../components/AppLayout";
Migrar para o Novo Cliente TypeScript Gerado
Seção intitulada “Migrar para o Novo Cliente TypeScript Gerado”Quase lá! Agora migraremos para usar o cliente TypeScript gerado pelo Nx Plugin para AWS, que tem melhorias em relação ao Type Safe API. Siga os passos:
-
Importe o novo cliente gerado em vez do antigo:
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";Nota:
routes/$shoppingListId.tsx
importaShoppingList
como_ShoppingList
- mantenha isso importando detypes.gen
. -
Instancie os novos hooks do 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: (p) => p.nextToken },),);const putShoppingList = useMutation(api.putShoppingList.mutationOptions());const deleteShoppingList = useMutation(api.deleteShoppingList.mutationOptions(),); -
Remova o wrapper
<operation>RequestContent
em chamadas:await putShoppingList.mutateAsync({putShoppingListRequestContent: {name: item,},});
Migrar de TanStack Query v4 para v5
Seção intitulada “Migrar de TanStack Query v4 para v5”Ajustes finais devido a diferenças entre as versões 4 e 5 do TanStack Query:
-
Substitua
isLoading
porisPending
em mutações:putShoppingList.isLoadingputShoppingList.isPending -
O
InfiniteQueryTable
do@aws-northstar/ui
espera tipos da v4. Podemos suprimir o erro de tipo:<InfiniteQueryTablequery={getShoppingLists}query={getShoppingLists as any}
Acessar o Website Local
Seção intitulada “Acessar o Website Local”Agora você pode acessar o website local em http://localhost:4200/
O website deve carregar após todas as migrações! Como a aplicação só depende da tabela DynamoDB além de API, Website e Identity, se você tiver uma tabela shopping_list
na região e credenciais AWS locais com acesso, tudo funcionará!
Caso contrário, sem problemas - migraremos a infraestrutura a seguir.
Migração da Página de Lista de Compras
Página de Listas de Compras
/* eslint-disable @typescript-eslint/no-floating-promises */... (código original em inglês)
/* eslint-disable @typescript-eslint/no-floating-promises */... (código migrado em inglês)
Página de Lista de Compras
/* eslint-disable @typescript-eslint/no-floating-promises */... (código original em inglês)
// routes/$shoppingListId.tsx/* eslint-disable @typescript-eslint/no-floating-promises */... (código migrado em inglês)
Migrar a Infraestrutura
Seção intitulada “Migrar a Infraestrutura”O último projeto que precisamos migrar para nossa aplicação de lista de compras é o InfrastructureTsProject
. Este é um projeto TypeScript CDK, para o qual o equivalente no Nx Plugin for AWS é o gerador ts#infra
.
Assim como os projetos Projen, o PDK também fornecia constructs CDK dos quais esses projetos dependem. Migraremos a aplicação de lista de compras desses constructs CDK também, em favor dos gerados pelo Nx Plugin for AWS.
Gerar um Projeto de Infraestrutura TypeScript CDK
Seção intitulada “Gerar um Projeto de Infraestrutura TypeScript CDK”Execute o gerador ts#infra
para configurar seu projeto de infraestrutura em packages/infra
:
- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - ts#infra
- Preencha os parâmetros obrigatórios
- name: infra
- Clique em
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
Você também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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 a Infraestrutura CDK
Seção intitulada “Migrar a Infraestrutura CDK”A aplicação de lista de compras PDK instanciava os seguintes constructs dentro da stack de aplicação CDK:
DatabaseConstruct
para a tabela DynamoDB que armazena as listas de comprasUserIdentity
para recursos Cognito, importado diretamente do PDKMyApi
para implantar a API Smithy, que usava o construct CDK TypeScript gerado com integrações type-safe, dependendo do construct CDKTypeSafeRestApi
do PDK internamente.Website
para implantar o Website, encapsulando o construct CDKStaticWebsite
do PDK.
A seguir, migraremos cada um desses para o novo projeto.
Copiar a Stack de Aplicação
Seção intitulada “Copiar a Stack de Aplicação”Copie packages/infra/src/stacks/application-stack.ts
da aplicação de lista de compras PDK para exatamente o mesmo local em seu novo projeto. Você verá alguns erros TypeScript que abordaremos abaixo.
Copiar o Construct de Banco de Dados
Seção intitulada “Copiar o Construct de Banco de Dados”A aplicação PDK tinha um construct Database
em packages/src/constructs/database.ts
. Copie este arquivo para o mesmo local no novo projeto.
Como o Nx Plugin for AWS usa Checkov para testes de segurança que é um pouco mais rigoroso que o PDK Nag, precisamos adicionar algumas supressões:
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',);
Em application-stack.ts
, atualize a importação do DatabaseConstruct
para usar sintaxe ESM:
import { DatabaseConstruct } from '../constructs/database';import { DatabaseConstruct } from '../constructs/database.js';
Migrar o Construct UserIdentity
Seção intitulada “Migrar o Construct UserIdentity”O construct UserIdentity
geralmente pode ser substituído sem alterações ajustando as importações.
import { UserIdentity } from "@aws/pdk/identity";import { UserIdentity } from ':shopping-list/common-constructs';...const userIdentity = new UserIdentity(this, `${id}UserIdentity`);
Note que os constructs subjacentes usados pelo novo construct UserIdentity
são fornecidos diretamente do aws-cdk-lib
, enquanto o PDK usava @aws-cdk/aws-cognito-identitypool-alpha
.
Migrar o Construct de API
Seção intitulada “Migrar o Construct de API”A aplicação PDK tinha um construct em constructs/apis/myapi.ts
que instanciava um construct CDK gerado pelo Type Safe API a partir do modelo Smithy.
Além deste construct, como o projeto PDK usava a trait @handler
, constructs CDK de funções lambda geradas também eram criados.
Assim como o Type Safe API, o Nx Plugin for AWS fornece type-safety para integrações baseadas no modelo Smithy, porém de forma mais simples e flexível. Em vez de gerar um construct CDK inteiro em tempo de build, apenas “metadados” mínimos são gerados, que o packages/common/constructs/src/app/apis/api.ts
usa de forma genérica. Você pode saber mais no guia do gerador ts#smithy-api
.
Siga os passos abaixo:
-
Instancie o construct
Api
emapplication-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(),});Observe que usamos
Api.defaultIntegrations(this).build()
- o comportamento padrão é criar uma função lambda para cada operação em nossa API, mesmo comportamento que tínhamos emmyapi.ts
. -
Conceda permissões para as funções lambda acessarem a tabela DynamoDB.
Na aplicação PDK, o
DatabaseConstruct
era passado paraMyApi
, que gerenciava a adição das permissões relevantes a cada função gerada. Faremos isso diretamente emapplication-stack.ts
acessando a propriedade type-safeintegrations
do constructApi
:stacks/application-stack.ts // Concede acesso escopo às funções lambda para chamar DynamodatabaseConstruct.shoppingListTable.grantReadData(api.integrations.getShoppingLists.handler,);[api.integrations.putShoppingList.handler,api.integrations.deleteShoppingList.handler,].forEach((f) => databaseConstruct.shoppingListTable.grantWriteData(f)); -
Conceda permissões para usuários autenticados invocarem a API.
Dentro do
myapi.ts
da aplicação PDK, usuários autenticados também tinham permissões IAM para invocar a API. Faremos o equivalente emapplication-stack.ts
:stacks/application-stack.ts api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
Migrar o Construct Website
Seção intitulada “Migrar o Construct Website”Finalmente, adicionamos o construct Website
de packages/common/constructs/src/app/static-websites/website.ts
em application-stack.ts
, pois é o equivalente ao packages/infra/src/constructs/websites/website.ts
da aplicação PDK.
import { Website } from "../constructs/websites/website";import { Website } from ':shopping-list/common-constructs';...new Website(this, "Website", { userIdentity, myapi,});new Website(this, 'Website');
Note que não passamos a identidade ou API para o website - o config runtime é gerenciado dentro de cada construct fornecido pelo Nx Plugin for AWS, onde UserIdentity
e Api
registram os valores necessários, e Website
gerencia sua implantação em /runtime-config.json
no website estático.
Vamos construir o projeto agora que migramos todas as partes relevantes do código para nosso novo projeto.
pnpm nx run-many --target build
yarn nx run-many --target build
npx nx run-many --target build
bunx nx run-many --target build
Implantar
Seção intitulada “Implantar”Agora que temos nossa base de código totalmente migrada, podemos pensar em implantá-la. Existem dois caminhos que podemos seguir neste momento.
Recursos Totalmente Novos (Simples)
Seção intitulada “Recursos Totalmente Novos (Simples)”A abordagem mais simples é tratar isso como um aplicativo completamente novo, significando que vamos “começar do zero” com uma nova tabela DynamoDB e um novo User Pool do Cognito - perdendo todos os usuários e suas listas de compras. Para esta abordagem, basta:
-
Exclua a tabela DynamoDB chamada
shopping_list
-
Implante a nova aplicação:
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/*
🎉 E pronto! 🎉
Migrar Recursos Estáveis Existentes sem Tempo de Inatividade (Mais Complexo)
Seção intitulada “Migrar Recursos Estáveis Existentes sem Tempo de Inatividade (Mais Complexo)”Na realidade, é mais provável que você queira migrar os recursos existentes da AWS para que sejam gerenciados pela nova base de código, evitando qualquer tempo de inatividade para seus clientes.
Para nossa aplicação de lista de compras, os recursos estáticos importantes são a tabela DynamoDB que contém as listas de compras dos usuários e o User Pool do Cognito que contém os detalhes de todos os usuários registrados. Nosso plano de alto nível será manter esses dois recursos-chave e movê-los para que sejam gerenciados por nosso novo stack, então atualizar o DNS para apontar para o novo site (e API, se exposta aos clientes).
-
Atualize sua nova aplicação para referenciar os recursos existentes que deseja manter.
Para a aplicação de lista de compras, fazemos isso para a tabela DynamoDB
constructs/database.ts this.shoppingListTable = new Table(this, 'ShoppingList', {...this.shoppingListTable = Table.fromTableName(this,'ShoppingList','shopping_list',);E para o User Pool do Cognito
packages/common/constructs/src/core/user-identity.ts this.userPool = this.createUserPool();this.userPool = UserPool.fromUserPoolId(this,'UserPool','<your-user-pool-id>',); -
Construa e implante a nova aplicação:
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/*Agora temos nossa nova aplicação implantada referenciando os recursos existentes, mas ainda não recebendo tráfego.
-
Realize testes completos de integração para garantir que a nova aplicação funciona conforme esperado. Para a aplicação de lista de compras, acesse o site e verifique se é possível fazer login, criar, visualizar, editar e excluir listas.
-
Reverta as alterações que referenciam os recursos existentes em sua nova aplicação, mas não as implante ainda.
constructs/database.ts this.shoppingListTable = new Table(this, 'ShoppingList', {...this.shoppingListTable = Table.fromTableName(this,'ShoppingList','shopping_list',);E para o User Pool do Cognito
packages/common/constructs/src/core/user-identity.ts this.userPool = this.createUserPool();this.userPool = UserPool.fromUserPoolId(this,'UserPool','<your-user-pool-id>',);Em seguida, execute uma build
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 -
Use
cdk import
na pastapackages/infra
de sua nova aplicação para ver quais recursos serão importados.New Application cd packages/infrapnpm exec cdk import shopping-list-infra-sandbox/Application --forceSiga os prompts pressionando Enter. A importação falhará porque os recursos são gerenciados por outro stack - isso é esperado, fizemos este passo apenas para confirmar quais recursos precisaremos manter. Você verá uma saída como:
Terminal window shopping-list-infra-sandbox/Application/ApplicationUserIdentity/UserPool/smsRole/Resource (AWS::IAM::Role): insira RoleName (vazio para pular)shopping-list-infra-sandbox/Application/ApplicationUserIdentity/UserPool/Resource (AWS::Cognito::UserPool): insira UserPoolId (vazio para pular)shopping-list-infra-sandbox/Application/Database/ShoppingList/Resource (AWS::DynamoDB::Table): importar com TableName=shopping_list (s/n) sIsso nos diz que há 3 recursos que precisaremos importar para nosso novo stack.
-
Atualize seu projeto PDK antigo para definir
RemovalPolicy
comoRETAIN
para os recursos descobertos no passo anterior. Atualmente, este é o padrão para o User Pool e a tabela DynamoDB, mas precisamos atualizar para a SMS Role descoberta: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); -
Implante seu projeto PDK para aplicar as políticas de remoção
PDK Application cd packages/infranpx projen deploy -
Acesse o console do CloudFormation e registre os valores solicitados no passo anterior do
cdk import
- O ID do User Pool, ex:
us-west-2_XXXXX
- O Nome da SMS Role, ex:
infra-sandbox-UserIdentityUserPoolsmsRoleXXXXXX
- O ID do User Pool, ex:
-
Atualize seu projeto PDK para referenciar os recursos existentes em vez de criá-los
constructs/database.ts this.shoppingListTable = new Table(this, 'ShoppingList', {...this.shoppingListTable = Table.fromTableName(this,'ShoppingList','shopping_list',);E para o User Pool do Cognito
application-stack.ts const userPool = UserPool.fromUserPoolId(this,'UserPool','<your-user-pool-id>',);const userIdentity = new UserIdentity(this, `${id}UserIdentity`, {// O construct PDK aceita UserPool não IUserPool, mas ainda funciona!userPool: userPool as any,}); -
Implante seu projeto PDK novamente, isso fará com que os recursos não sejam mais gerenciados pelo stack CloudFormation do PDK.
PDK Application cd packages/infranpx projen deploy -
Agora que os recursos não estão mais sendo gerenciados, podemos executar
cdk import
em nossa nova aplicação para realizar a importação:New Application cd packages/infrapnpm exec cdk import shopping-list-infra-sandbox/Application --forceInsira os valores quando solicitado, a importação deve ser concluída com sucesso.
-
Implante a nova aplicação novamente para garantir que quaisquer alterações nesses recursos existentes (agora gerenciados pelo novo stack) sejam aplicadas:
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/* -
Realize testes completos novamente na nova aplicação
-
Atualize os registros DNS para apontar para seu novo Site (e API se necessário).
Recomendamos uma abordagem gradual usando Roteamento Ponderado do Route53, onde uma fração das requisições é direcionada para a nova aplicação inicialmente. Monitore suas métricas e aumente gradualmente o peso para a nova aplicação até que nenhum tráfego seja enviado para a aplicação PDK antiga.
Se você não tiver DNS e usou os domínios auto-gerados para o site e API, pode considerar o uso de proxy de requisições (ex: via origem HTTP CloudFront ou integrações HTTP do API Gateway).
-
Monitore as métricas da aplicação PDK para garantir que não há tráfego e finalmente destrua o stack CloudFormation antigo:
Terminal window cd packages/infranpx projen destroy
Isso foi bem mais complexo, mas migramos nossos usuários perfeitamente para a nova aplicação! 🎉🎉🎉
Agora temos os benefícios do Nx Plugin para AWS em relação ao PDK:
- Builds mais rápidas
- Suporte a desenvolvimento local de API
- Base de código amigável para vibe-coding (experimente nosso servidor MCP!)
- Código type-safe mais intuitivo para cliente/servidor
- E muito mais!
Perguntas Frequentes
Seção intitulada “Perguntas Frequentes”Esta seção fornece orientações para recursos do PDK não cobertos pelo exemplo acima.
Como regra geral ao migrar do PDK, recomendamos começar qualquer projeto com um Workspace Nx, dada sua similaridade com o Monorepo PDK. Também recomendamos usar nossos geradores como primitivas para construir novos 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
Seção intitulada “CDK Graph”O CDK Graph constrói grafos de seus recursos CDK conectados e fornece dois plugins:
Diagram Plugin
Seção intitulada “Diagram Plugin”O CDK Graph Diagram Plugin gera diagramas de arquitetura AWS a partir de sua infraestrutura CDK.
Para uma abordagem determinística similar, uma alternativa viável é o CDK-Dia.
Com os avanços em IA Generativa, muitos modelos de base são capazes de criar diagramas de alta qualidade a partir de sua infraestrutura CDK. Recomendamos experimentar o AWS Diagram MCP Server. Confira este post de blog para um passo a passo.
Threat Composer Plugin
Seção intitulada “Threat Composer Plugin”O CDK Graph Threat Composer Plugin gera um modelo de ameaça inicial para o Threat Composer a partir de seu código CDK.
Este plugin funciona simplesmente filtrando um modelo de ameaça base contendo ameaças de exemplo, e filtrando-as com base nos recursos utilizados por sua stack.
Se você está interessado nessas ameaças de exemplo específicas, pode copiar e filtrar o modelo de ameaça base, ou usá-lo como contexto para ajudar um modelo de base a gerar um similar.
AWS Arch
Seção intitulada “AWS Arch”AWS Arch fornece mapeamentos entre recursos do CloudFormation e seus ícones de arquitetura associados para o CDK Graph acima.
Consulte a AWS Architecture Icons page para recursos relacionados a ícones. Diagrams também oferece uma forma de construir diagramas como código.
Se você estiver usando isso diretamente, considere fazer um fork do projeto e assumir a responsabilidade!
Pipeline
Seção intitulada “Pipeline”O PDK fornecia um PDKPipelineProject
que configurava um projeto de infraestrutura CDK e utilizava um construto CDK que encapsulava alguns recursos de CDK Pipelines.
Para migrar disso, você pode usar os construtos de CDK Pipelines diretamente. Na prática, porém, é provavelmente mais simples usar algo como GitHub Actions ou GitLab CI/CD, onde você define CDK Stages e executa o comando de deploy para o estágio apropriado diretamente.
PDK Nag
Seção intitulada “PDK Nag”O PDK Nag envolve o CDK Nag e fornece um conjunto de regras específicas para construção de protótipos.
Para migrar do PDK Nag, use o CDK Nag diretamente. Se você precisa do mesmo conjunto de regras, pode criar seu próprio “pack” seguindo a documentação aqui.
Type Safe API
Seção intitulada “Type Safe API”Os componentes mais utilizados do Type Safe API foram abordados no exemplo de migração acima, porém existem outros recursos cujos detalhes de migração estão abaixo.
APIs Modeladas com OpenAPI
Seção intitulada “APIs Modeladas com OpenAPI”O Plugin Nx para AWS suporta APIs modeladas em Smithy, mas não aquelas modeladas diretamente em OpenAPI. O gerador ts#smithy-api
é um bom ponto de partida que você pode modificar posteriormente. Você pode definir sua especificação OpenAPI na pasta src
do projeto model
em vez de Smithy, e modificar o build.Dockerfile
para usar sua ferramenta de geração de código desejada para clientes/servidores caso elas não estejam disponíveis no NPM. Se suas ferramentas desejadas estiverem no NPM, você pode instalá-las como dependências de desenvolvimento no workspace Nx e chamá-las diretamente como alvos de build do Nx.
Backend
Seção intitulada “Backend”Para backends type-safe modelados em OpenAPI, você pode considerar usar um dos OpenAPI Generator Server Generators. Estes não geram código diretamente para AWS Lambda, mas você pode usar o AWS Lambda Web Adapter para preencher essa lacuna para muitos deles.
Para Python, o gerador python-fastapi pode ser usado como uma ferramenta pontual para ajudar na migração do Type Safe API para nosso gerador py#fast-api
.
Para clientes TypeScript, você pode usar o gerador ts#react-website
e o gerador api-connection
com um exemplo ts#smithy-api
para ver como os clientes são gerados e integrados a um website. Isso configura alvos de build que geram clientes invocando nossos geradores open-api#ts-client
ou open-api#ts-hooks
. Você pode usar esses geradores apontando-os para sua Especificação OpenAPI.
Para outras linguagens, você também pode verificar se algum dos geradores do OpenAPI Generator atende às suas necessidades.
Você também pode construir um gerador personalizado usando o gerador ts#nx-generator
. Consulte a documentação desse gerador para detalhes sobre como gerar código a partir do OpenAPI. Você pode usar os templates do Plugin Nx para AWS como ponto de partida. Também pode consultar os templates da codebase do PDK para mais inspiração, notando que a estrutura de dados dos templates é um pouco diferente do Plugin Nx para AWS.
APIs Modeladas com TypeSpec
Seção intitulada “APIs Modeladas com TypeSpec”Para TypeSpec, a seção acima sobre OpenAPI também se aplica. Você pode começar gerando um ts#smithy-api
, instalar o compilador TypeSpec e pacotes OpenAPI em seu workspace Nx, e atualizar o alvo compile
do projeto model para executar tsp compile
em vez disso, garantindo que ele gere uma especificação OpenAPI no diretório dist
.
Backend
Seção intitulada “Backend”A abordagem recomendada seria usar o TypeSpec HTTP Server generator for JavaScript para gerar seu código de servidor, pois isso funciona diretamente em seu modelo TypeSpec.
Você pode usar o AWS Lambda Web Adapter para executar o servidor gerado no AWS Lambda.
Também pode usar qualquer uma das opções OpenAPI mencionadas acima.
O TypeSpec possui seus próprios geradores de código para clientes nas três linguagens suportadas pelo Type Safe API:
A seção OpenAPI acima também se aplica, pois o TypeSpec pode compilar para OpenAPI.
APIs Modeladas com Smithy
Seção intitulada “APIs Modeladas com Smithy”O exemplo de migração acima descreve a migração para usar o gerador ts#smithy-api
. Esta seção aborda as opções para backends e clientes em Python e Java.
Backend
Seção intitulada “Backend”O Smithy code generator for Java. Isso inclui um gerador de servidor Java bem como um adaptador para executar o servidor Java gerado no AWS Lambda.
O Smithy não possui um gerador de servidor para Python, então você precisará usar OpenAPI. Consulte a seção acima sobre APIs Modeladas com OpenAPI para opções possíveis.
O Smithy code generator for Java. Isso inclui um gerador de cliente Java.
Para clientes Python, você pode verificar o Smithy Python.
Para TypeScript, consulte Smithy TypeScript, ou use a mesma abordagem que adotamos no ts#smithy-api
via OpenAPI (optamos por isso para ter consistência entre APIs tRPC, FastAPI e Smithy via hooks TanStack Query).
Smithy Shape Library
Seção intitulada “Smithy Shape Library”O Type Safe API fornecia um tipo de projeto Projen chamado SmithyShapeLibraryProject
que configurava um projeto contendo modelos Smithy reutilizáveis por múltiplas APIs baseadas em Smithy.
A maneira mais direta de alcançar isso é:
Criar uma Biblioteca de Formas
Seção intitulada “Criar uma Biblioteca de Formas”-
Crie sua biblioteca de formas usando o gerador
smithy#project
:- Instale o Nx Console VSCode Plugin se ainda não o fez
- Abra o console Nx no VSCode
- Clique em
Generate (UI)
na seção "Common Nx Commands" - Procure por
@aws/nx-plugin - smithy#project
- Preencha os parâmetros obrigatórios
- Clique em
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#projectVocê também pode realizar uma execução simulada para ver quais arquivos seriam alterados
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-runEspecifique qualquer nome para a opção
serviceName
, pois removeremos a formaservice
. -
Substitua o modelo padrão em
src
pelas formas que deseja definir -
Atualize
smithy-build.json
para removerplugins
e quaisquer dependências maven não utilizadas -
Substitua
build.Dockerfile
por etapas de build mínimas: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 a Biblioteca de Formas
Seção intitulada “Consumir a Biblioteca de Formas”Em seu(s) projeto(s) de modelo de serviço, faça as seguintes alterações para consumir a biblioteca de formas:
-
Atualize o alvo
compile
emproject.json
para adicionar o workspace como contexto de build e uma dependência no alvobuild
da 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"]} -
Atualize o
build.Dockerfile
para copiar o diretóriosrc
de sua biblioteca de formas. Por exemplo, assumindo que a biblioteca está empackages/shapes
:build.Dockerfile # Copy project filesCOPY smithy-build.json .COPY src srcCOPY --from=workspace packages/shapes/src shapes -
Atualize
smithy-build.json
para adicionar o diretório de formas em suassources
:smithy-build.json {"version": "1.0","sources": ["src/", "shapes/"],"plugins": {...}
Interceptores
Seção intitulada “Interceptores”O Type Safe API fornecia os seguintes interceptores padrão:
- Interceptores de logging, tracing e métricas usando Powertools para AWS Lambda
- Interceptor try-catch para tratamento de exceções não capturadas
- Interceptor CORS para retornar headers CORS
O gerador ts#smithy-api
instrumenta logging, tracing e métricas com Powertools para AWS Lambda usando Middy. O comportamento do interceptor try-catch está embutido no Smithy TypeScript SSDK, e os headers CORS são adicionados no handler.ts
.
Para interceptores de logging, tracing e métricas em qualquer linguagem, use Powertools para AWS Lambda diretamente.
Para migrar interceptores personalizados, recomendamos usar as seguintes bibliotecas:
- TypeScript - Middy
- Python - Powertools for AWS Lambda Middleware Factory
- Java - Instrumente métodos antes/depois de sua lógica de negócios usando aws-lambda-java-libs para uma abordagem simples, ou considere AspectJ para construir middlewares como anotações.
Geração de Documentação
Seção intitulada “Geração de Documentação”O Type Safe API fornecia geração de documentação usando Redocly CLI. Isso é fácil de adicionar a um projeto existente após a migração.
-
Instale o 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 -
Adicione um alvo de geração de documentação ao seu projeto
model
usandoredocly build-docs
, por exemplo: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"]}}
Você também pode considerar os OpenAPI Generator documentation generators.
Mock Integrations
Seção intitulada “Mock Integrations”O Type Safe API gerava mocks para você dentro de seu pacote de infraestrutura gerado.
Você pode migrar para JSON Schema Faker que pode criar dados mock baseados em JSON Schemas. Isso pode funcionar diretamente em uma especificação OpenAPI, e possui uma CLI que você pode executar como parte do build do projeto model
.
Você pode atualizar sua infraestrutura CDK para ler o arquivo JSON gerado pelo JSON Schema Faker e retornar o MockIntegration
apropriado do API Gateway para uma integração, baseado no metadata.gen.ts
gerado (assumindo que você usou o gerador ts#smithy-api
).
Backends em Múltiplas Linguagens
Seção intitulada “Backends em Múltiplas Linguagens”O Type Safe API suportava implementar APIs com uma mistura de diferentes linguagens no backend. Isso também pode ser alcançado fornecendo “overrides” para integrações ao instanciar seu construto de API no 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(),});
Você precisará “stubar” seu serviço/router para compilar seu serviço se estiver usando o ts#smithy-api
e o TypeScript Server SDK, ex:
export const Service: ApiService<ServiceContext> = { ... Echo: () => { throw new Error(`Not Implemented`); },};
Validação de Entrada
Seção intitulada “Validação de Entrada”O Type Safe API adicionava validação nativa do API Gateway para corpos de requisição baseado em sua especificação OpenAPI, pois usava o construto SpecRestApi
.
Com o gerador ts#smithy-api
, a validação é realizada pelo próprio Server SDK. O mesmo vale para a maioria dos geradores de servidor.
Se desejar implementar validação nativa do API Gateway, você pode modificar packages/common/constructs/src/core/api/rest-api.ts
para ler o JSON schema relevante para o corpo da requisição de cada operação a partir de sua especificação OpenAPI.
APIs WebSocket
Seção intitulada “APIs WebSocket”Infelizmente não há um caminho direto de migração para APIs WebSocket do Type Safe API usando API Gateway e Lambda com desenvolvimento de API orientado a modelos. Porém, esta seção visa oferecer algumas ideias.
Considere usar AsyncAPI para modelar sua API em vez de OpenAPI ou TypeSpec, pois é projetado para APIs assíncronas. O AsyncAPI NodeJS Template pode gerar um backend WebSocket Node que você pode hospedar no ECS, por exemplo.
Você também pode considerar AppSync Events para infraestrutura, e usar Powertools. Este post vale a leitura!
Outra opção é usar APIs GraphQL com WebSocket no AppSync, para o qual temos um GitHub issue que você pode +1! Consulte o AppSync developer guide para detalhes e links para projetos de exemplo.
Você também pode considerar criar seus próprios geradores de código que interpretam as mesmas extensões de vendor do Type Safe API. Consulte a seção APIs Modeladas com OpenAPI para detalhes sobre como construir geradores de código baseados em OpenAPI. Você pode encontrar os templates que o Type Safe API usa para handlers Lambda de API Gateway WebSocket aqui, e o cliente aqui.
Você também pode considerar migrar para usar o gerador ts#trpc-api
para usar tRPC. No momento não temos suporte para subscriptions/streaming, mas se precisar, adicione +1 em nosso GitHub issue.
Smithy é agnóstico a protocolos, mas ainda não tem suporte para WebSocket, consulte este GitHub issue.
Infraestrutura em Python ou Java
Seção intitulada “Infraestrutura em Python ou Java”O PDK suporta infraestrutura CDK escrita em Python e Java. Atualmente, não oferecemos suporte a isso no Nx Plugin para AWS.
A abordagem recomendada seria migrar sua infraestrutura CDK para TypeScript ou usar nossos geradores e migrar o pacote de constructos comuns para sua linguagem desejada. Você pode usar IA Generativa para acelerar esse tipo de migração, por exemplo o Amazon Q CLI. Um agente de IA pode iterar na migração até que os modelos CloudFormation sintetizados sejam idênticos.
O mesmo se aplica à infraestrutura gerada pelas Type Safe APIs em Python ou Java - você pode traduzir o constructo genérico rest-api.ts
do pacote de constructos comuns e implementar seu próprio gerador de metadados simples para a linguagem alvo (consulte a seção APIs Modeladas com OpenAPI).
Você pode usar o gerador py#project
como base para projetos Python e adicionar seu código CDK (além de mover seu arquivo cdk.json
e adicionar targets relevantes). Para projetos Java, utilize o plugin @nx/gradle
do Nx ou @jnxplus/nx-maven
para Maven.
Uso do Projen
Seção intitulada “Uso do Projen”O PDK foi construído sobre o Projen. O Projen e os Geradores do Nx possuem diferenças bastante fundamentais, o que significa que, embora seja tecnicamente possível combiná-los, isso provavelmente é um antipadrão. O Projen gerencia arquivos de projeto como código de forma que eles não podem ser modificados diretamente, enquanto os geradores do Nx geram arquivos de projeto uma vez e, em seguida, o código pode ser modificado livremente.
Se você deseja continuar usando o Projen, pode implementar os tipos de projeto Projen desejados por conta própria. Para seguir os padrões do Nx Plugin for AWS, você pode executar nossos geradores ou examinar seu código-fonte no GitHub para ver como os tipos de projeto desejados são construídos e implementar as partes relevantes usando os primitivos do Projen.