Migrazione da AWS PDK
Questa guida ti accompagna in una migrazione di esempio di un progetto AWS PDK al Plugin Nx per AWS, oltre a fornire indicazioni generali sull’argomento.
La migrazione al Plugin Nx per AWS offre i seguenti vantaggi rispetto a PDK:
- Build più veloci
- Facilità d’uso (UI e CLI)
- Compatibile con il vibe-coding (prova il nostro server MCP!)
- Tecnologie più moderne
- Sviluppo locale di API e siti web
- Maggiore controllo (modifica i file forniti per adattarli al tuo caso d’uso)
- E altro ancora!
Migrazione di esempio: Applicazione Lista della Spesa
Sezione intitolata “Migrazione di esempio: Applicazione Lista della Spesa”In questa guida utilizzeremo l’Applicazione Lista della Spesa del Tutorial PDK come progetto target da migrare. Segui i passaggi di quel tutorial per creare il progetto target se vuoi seguire la guida autonomamente.
L’applicazione lista della spesa consiste nei seguenti tipi di progetto PDK:
MonorepoTsProject
TypeSafeApiProject
CloudscapeReactTsWebsiteProject
InfrastructureTsProject
Creare il Workspace
Sezione intitolata “Creare il Workspace”Per iniziare, creeremo un nuovo workspace per il nostro nuovo progetto. Sebbene più drastico di una migrazione in-place, questo approccio garantisce il risultato finale più pulito. Creare un workspace Nx è equivalente all’uso di MonorepoTsProject
di 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
Apri la directory shopping-list
creata da questo comando nel tuo IDE preferito.
Migrare l’API
Sezione intitolata “Migrare l’API”Il TypeSafeApiProject
utilizzato nell’applicazione della lista della spesa ha fatto uso di:
- Smithy come linguaggio di modellazione
- TypeScript per l’implementazione delle operazioni
- Generazione di hook TypeScript per l’integrazione con un sito web React
Possiamo quindi utilizzare il generatore ts#smithy-api
per fornire funzionalità equivalenti.
Genera un’API Smithy TypeScript
Sezione intitolata “Genera un’API Smithy TypeScript”Esegui il generatore ts#smithy-api
per configurare il tuo progetto API in packages/api
:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - ts#smithy-api
- Compila i parametri richiesti
- name: api
- namespace: com.aws
- auth: IAM
- Clicca su
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
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
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
Noterai che vengono generati un progetto model
e un progetto backend
. Il progetto model
contiene il modello Smithy, mentre backend
contiene l’implementazione del server.
Il backend utilizza lo Smithy Server Generator for TypeScript. Esploreremo questo aspetto più avanti.
Migra il modello Smithy
Sezione intitolata “Migra il modello Smithy”Ora che abbiamo la struttura base per il nostro progetto API Smithy, possiamo migrare il modello:
-
Elimina i file Smithy di esempio generati in
packages/api/model/src
-
Copia il tuo modello dalla directory
packages/api/model/src/main/smithy
del progetto PDK nella directorypackages/api/model/src
del nuovo progetto. -
Aggiorna il nome del servizio e il namespace in
smithy-build.json
per corrispondere all’applicazione PDK:smithy-build.json "plugins": {"openapi": {"service": "com.aws#MyApi",... -
Aggiorna il servizio in
main.smithy
per aggiungere l’erroreValidationException
, richiesto quando si utilizza lo Smithy TypeScript Server SDK.main.smithy use smithy.framework#ValidationException/// My Shopping List API@restJson1service MyApi {version: "1.0"operations: [GetShoppingListsPutShoppingListDeleteShoppingList]errors: [BadRequestErrorNotAuthorizedErrorInternalFailureErrorValidationException]} -
Aggiungi un file
extensions.smithy
inpackages/api/model/src
dove definiremo un tratto che fornisce informazioni di paginazione al client generato:extensions.smithy $version: "2"namespace com.awsuse smithy.openapi#specificationExtension@trait@specificationExtension(as: "x-cursor")structure cursor {inputToken: Stringenabled: Boolean} -
Aggiungi il nuovo tratto
@cursor
all’operazioneGetShoppingLists
inget-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}Qualsiasi operazione
@paginated
dovrebbe utilizzare anche@cursor
se stai usando il generatore di client fornito dal Nx Plugin for AWS (tramite il generatoreapi-connection
). -
Infine, rimuovi il tratto
@handler
da tutte le operazioni poiché non è supportato dal Nx Plugin for AWS. Utilizzandots#smithy-api
, non abbiamo bisogno dei costrutti CDK per le funzioni lambda auto-generate e dei target di bundling generati da questo tratto, poiché utilizziamo un singolo bundle per tutte le funzioni lambda.
A questo punto, eseguiamo una build per verificare le modifiche al modello e assicurarci di avere del codice server generato con cui lavorare. Ci saranno alcuni errori nel progetto backend (@shopping-list/api
) che affronteremo successivamente.
pnpm nx run-many --target build
yarn nx run-many --target build
npx nx run-many --target build
bunx nx run-many --target build
Migra gli handler Lambda
Sezione intitolata “Migra gli handler Lambda”Puoi considerare il progetto api/backend
come equivalente al progetto api/handlers/typescript
di Type Safe API.
Una delle principali differenze tra Type Safe API e il generatore ts#smithy-api
è che gli handler sono implementati usando lo Smithy Server Generator for TypeScript, anziché i wrapper handler generati da Type Safe API (presenti nel progetto api/generated/typescript/runtime
).
Gli handler lambda dell’applicazione lista della spesa fanno affidamento sul pacchetto @aws-sdk/client-dynamodb
, quindi installiamolo prima:
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
Poi, copiamo il file handlers/src/dynamo-client.ts
dal progetto PDK in backend/src/operations
per renderlo disponibile ai nostri handler.
Per migrare gli handler, puoi seguire questi passaggi generali:
-
Copia l’handler dalla directory
packages/api/handlers/typescript/src
del progetto PDK nella directorypackages/api/backend/src/operations
del nuovo progetto. -
Rimuovi gli import di
my-api-typescript-runtime
e importa invece il tipo dell’operazione dallo Smithy Server SDK generato, insieme aServiceContext
per esempio: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 l’esportazione del wrapper dell’handler
export const handler = deleteShoppingListHandler(...INTERCEPTORS,deleteShoppingList,); -
Aggiorna la firma del tuo handler per utilizzare lo SSDK:
export const deleteShoppingList: DeleteShoppingListChainedHandlerFunction = async (request) => {export const DeleteShoppingList: DeleteShoppingListOperation<ServiceContext> = async (input, ctx) => { -
Sostituisci l’uso di
LoggingInterceptor
conctx.logger
. (Si applica anche agli intercettori di metriche e tracing):LoggingInterceptor.getLogger(request).info('...');ctx.logger.info('...'); -
Aggiorna i riferimenti ai parametri di input. Poiché lo SSDK fornisce tipi che corrispondono esattamente al tuo modello Smithy (anziché raggruppare separatamente i parametri path/query/header dal parametro body), aggiorna di conseguenza i riferimenti agli input:
const shoppingListId = request.input.requestParameters.shoppingListId;const shoppingListId = input.shoppingListId; -
Rimuovi l’uso di
Response
. Invece restituiamo semplicemente oggetti semplici nello SSDK.return Response.success({ shoppingListId });return { shoppingListId };Inoltre, non lanciamo o restituiamo più
Response
, ma lanciamo gli errori generati dallo 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' }); -
Aggiorna gli import per utilizzare la sintassi ESM, aggiungendo l’estensione
.js
agli import relativi. -
Aggiungi l’operazione 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 qui le operazioni per il servizioexport const Service: MyApiService<ServiceContext> = {PutShoppingList,GetShoppingLists,DeleteShoppingList,};
Migrazione dei gestori della lista della spesa
Elimina lista della spesa
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, };};
Ottieni liste della spesa
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;
Aggiungi lista della spesa
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, };};
Abbiamo generato il progetto Smithy API con il nome api
inizialmente per posizionarlo in packages/api
in coerenza con il progetto PDK. Poiché la nostra API Smithy ora definisce service MyApi
invece di service Api
, dobbiamo aggiornare tutte le istanze di getApiServiceHandler
con getMyApiServiceHandler
.
Apporta questa modifica a 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 a 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);
Inoltre, aggiorna packages/api/backend/project.json
e modifica metadata.apiName
in my-api
:
"metadata": { "generator": "ts#smithy-api", "apiName": "api", "apiName": "my-api", "auth": "IAM", "modelProject": "@shopping-list/api-model", "ports": [3001] },
Verifica con una build
Sezione intitolata “Verifica con una build”Ora possiamo eseguire la build del progetto per verificare che la migrazione abbia funzionato finora:
pnpm nx run-many --target build
yarn nx run-many --target build
npx nx run-many --target build
bunx nx run-many --target build
Migrare il Sito Web
Sezione intitolata “Migrare il Sito Web”Il progetto CloudscapeReactTsWebsiteProject
utilizzato nell’applicazione della lista della spesa configurava un sito web React con integrazione nativa di CloudScape e autenticazione Cognito.
Questo tipo di progetto si basava su create-react-app
, ora deprecato. Per migrare il sito web in questa guida, utilizzeremo il generatore ts#react-website
, che impiega tecnologie più moderne e supportate, in particolare Vite.
Come parte della migrazione, passeremo anche dal React Router configurato da PDK a TanStack Router, che aggiunge maggiore type-safety al routing del sito web.
Genera un Sito Web React
Sezione intitolata “Genera un Sito Web React”Esegui il generatore ts#react-website
per configurare il progetto del sito web in packages/website
:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - ts#react-website
- Compila i parametri richiesti
- name: website
- Clicca su
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
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
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
Aggiungi Autenticazione Cognito
Sezione intitolata “Aggiungi Autenticazione Cognito”Il generatore per siti web React sopra citato non include l’autenticazione Cognito di default come CloudscapeReactTsWebsiteProject
, ma può essere aggiunta esplicitamente tramite il generatore ts#react-website#auth
.
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - ts#react-website#auth
- Compila i parametri richiesti
- project: website
- cognitoDomain: shopping-list
- Clicca su
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
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
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
Questo aggiunge componenti React che gestiscono i redirect necessari per garantire il login degli utenti tramite l’UI ospitata da Cognito. Viene inoltre aggiunto un costrutto CDK per distribuire le risorse Cognito in packages/common/constructs
, chiamato UserIdentity
.
Collega il Sito Web all’API
Sezione intitolata “Collega il Sito Web all’API”In PDK era possibile passare i progetti Projen configurati tra loro per attivare l’integrazione. Questo veniva utilizzato nell’applicazione della lista della spesa per configurare l’integrazione tra sito web e API.
Con il Plugin Nx per AWS, l’integrazione API è supportata tramite il generatore api-connection
. Utilizziamo ora questo generatore per permettere al nostro sito web di invocare la nostra API Smithy:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - api-connection
- Compila i parametri richiesti
- sourceProject: website
- targetProject: api
- Clicca su
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
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
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
Questo genera i provider client necessari e i target di build affinché il tuo sito web possa chiamare l’API tramite un client TypeScript generato.
Aggiungi Dipendenza AWS Northstar
Sezione intitolata “Aggiungi Dipendenza AWS Northstar”Il CloudscapeReactTsWebsiteProject
includeva automaticamente una dipendenza da @aws-northstar/ui
utilizzata nella nostra applicazione della lista della spesa, quindi la aggiungiamo qui:
pnpm add -w @aws-northstar/ui
yarn add @aws-northstar/ui
npm install --legacy-peer-deps @aws-northstar/ui
bun install @aws-northstar/ui
Sposta Componenti e Pagine
Sezione intitolata “Sposta Componenti e Pagine”L’applicazione della lista della spesa ha un componente chiamato CreateItem
e due pagine, ShoppingList
e ShoppingLists
. Migreremo questi elementi nel nuovo sito web, apportando alcune modifiche per l’utilizzo di TanStack Router e del generatore di client TypeScript del Plugin Nx per AWS.
-
Copia
packages/website/src/components/CreateItem/index.tsx
dal progetto PDK nella stessa posizione nel nuovo progetto. -
Copia
packages/website/src/pages/ShoppingLists/index.tsx
inpackages/website/src/routes/index.tsx
, poichéShoppingLists
è la nostra home page e utilizziamo il routing basato su file con TanStack Router. -
Copia
packages/website/src/pages/ShoppingList/index.tsx
inpackages/website/src/routes/$shoppingListId.tsx
, poichéShoppingList
è la pagina che vogliamo mostrare sulla rotta/:shoppingListId
.
Noterai alcuni errori di compilazione nell’IDE: apporteremo ulteriori modifiche per adattarci al nuovo framework, come descritto di seguito.
Migra da React Router a TanStack Router
Sezione intitolata “Migra da React Router a TanStack Router”Utilizzando il routing basato su file, possiamo usare il server di sviluppo locale per generare automaticamente la configurazione delle rotte. Avviamo il server locale del sito web:
pnpm nx serve-local website
yarn nx serve-local website
npx nx serve-local website
bunx nx serve-local website
Potresti vedere alcuni errori, ma il server locale del sito web dovrebbe avviarsi sulla porta 4200
, e il server Smithy API locale sulla porta 3001
.
Segui questi passaggi sia in routes/index.tsx
che routes/$shoppingListId.tsx
per migrare a TanStack Router:
-
Aggiungi
createFileRoute
per registrare ogni rotta: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,});Dopo il salvataggio, gli errori di tipo su
createFileRoute
dovrebbero scomparire. -
Sostituisci l’hook
useNavigate
.Aggiorna l’import:
import { useNavigate } from 'react-router-dom';import { useNavigate } from '@tanstack/react-router';Aggiorna le chiamate al metodo
navigate
per utilizzare le rotte type-safe:navigate(`/${cell.shoppingListId}`);navigate({to: '/$shoppingListId',params: { shoppingListId: cell.shoppingListId },}); -
Sostituisci l’hook
useParams
.Rimuovi l’import:
import { useParams } from 'react-router-dom';Aggiorna le chiamate a
useParams
con l’hook fornito dallaRoute
creata:const { shoppingListId } = useParams();const { shoppingListId } = Route.useParams();
Corregge gli Import dei Componenti
Sezione intitolata “Corregge gli Import dei Componenti”Dato che i file delle rotte non sono più annidati come nel progetto PDK, correggiamo l’import di CreateItem
in entrambi i file delle rotte:
import CreateItem from "../../components/CreateItem";import CreateItem from "../components/CreateItem";
Anche il contesto AppLayoutContext
si trova in una posizione leggermente diversa:
import { AppLayoutContext } from "../../layouts/App";import { AppLayoutContext } from "../components/AppLayout";
Migra al Nuovo Client TypeScript Generato
Sezione intitolata “Migra al Nuovo Client TypeScript Generato”Ora migriamo al client TypeScript generato dal Plugin Nx per AWS, che offre diversi miglioramenti rispetto a Type Safe API:
-
Importa il nuovo client generato invece del vecchio:
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"; -
Instanzia i nuovi hook 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(),); -
Rimuovi i wrapper
<operation>RequestContent
per le chiamate:await putShoppingList.mutateAsync({putShoppingListRequestContent: {name: item,},});
Migra da TanStack Query v4 a v5
Sezione intitolata “Migra da TanStack Query v4 a v5”Risolvi gli errori rimanenti dovuti alle differenze tra v4 e v5:
-
Sostituisci
isLoading
conisPending
per le mutazioni:putShoppingList.isLoadingputShoppingList.isPending -
Gestisci il tipo per
InfiniteQueryTable
:<InfiniteQueryTablequery={getShoppingLists}query={getShoppingLists as any}
Visita il Sito Web Locale
Sezione intitolata “Visita il Sito Web Locale”Ora puoi visitare il sito web locale all’indirizzo http://localhost:4200/
Il sito dovrebbe caricarsi correttamente dopo la migrazione! L’applicazione richiede solo l’infrastruttura API, Website, Identity e una tabella DynamoDB shopping_list
nella stessa regione con credenziali AWS locali valide. Altrimenti, procederemo con la migrazione dell’infrastruttura.
Migrazione Pagina Lista della Spesa
Migrare l’Infrastruttura
Sezione intitolata “Migrare l’Infrastruttura”L’ultimo progetto che dobbiamo migrare per la nostra applicazione della lista della spesa è l’InfrastructureTsProject
. Si tratta di un progetto TypeScript CDK, per cui l’equivalente nel Plugin Nx per AWS è il generatore ts#infra
.
Oltre ai progetti Projen, PDK forniva anche costrutti CDK da cui questi progetti dipendevano. Migreremo l’applicazione della lista della spesa anche da questi costrutti CDK, a favore di quelli generati dal Plugin Nx per AWS.
Genera un Progetto di Infrastruttura TypeScript CDK
Sezione intitolata “Genera un Progetto di Infrastruttura TypeScript CDK”Esegui il generatore ts#infra
per configurare il tuo progetto di infrastruttura in packages/infra
:
- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - ts#infra
- Compila i parametri richiesti
- name: infra
- Clicca su
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
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
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
Migra l’Infrastruttura CDK
Sezione intitolata “Migra l’Infrastruttura CDK”L’applicazione PDK della lista della spesa istanziava i seguenti costrutti all’interno dello stack CDK:
DatabaseConstruct
per la tabella DynamoDB che memorizza le liste della spesaUserIdentity
per le risorse Cognito, importato direttamente da PDKMyApi
per distribuire l’API Smithy, che utilizzava il costrutto CDK TypeScript generato con integrazioni type-safe, basato sul costruttoTypeSafeRestApi
di PDK.Website
per distribuire il sito web, che incapsulava il costruttoStaticWebsite
di PDK.
Ora migreremo ciascuno di questi nel nuovo progetto.
Copia l’Application Stack
Sezione intitolata “Copia l’Application Stack”Copia packages/infra/src/stacks/application-stack.ts
dall’applicazione PDK della lista della spesa nella stessa identica posizione nel tuo nuovo progetto. Vedrai alcuni errori TypeScript che affronteremo di seguito.
Copia il Costrutto Database
Sezione intitolata “Copia il Costrutto Database”L’applicazione PDK aveva un costrutto Database
in packages/src/constructs/database.ts
. Copialo nella stessa posizione nel nuovo progetto.
Dato che il Plugin Nx per AWS utilizza Checkov per i test di sicurezza, che è leggermente più restrittivo di PDK Nag, dobbiamo aggiungere alcune soppressioni:
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',);
In application-stack.ts
, aggiorna l’import per DatabaseConstruct
con la sintassi ESM:
import { DatabaseConstruct } from '../constructs/database';import { DatabaseConstruct } from '../constructs/database.js';
Migra il Costrutto UserIdentity
Sezione intitolata “Migra il Costrutto UserIdentity”Il costrutto UserIdentity
può generalmente essere sostituito senza modifiche, aggiornando gli import.
import { UserIdentity } from "@aws/pdk/identity";import { UserIdentity } from ':shopping-list/common-constructs';...const userIdentity = new UserIdentity(this, `${id}UserIdentity`);
Nota che i costrutti sottostanti utilizzati dal nuovo UserIdentity
sono forniti direttamente da aws-cdk-lib
, mentre PDK utilizzava @aws-cdk/aws-cognito-identitypool-alpha
.
Migra il Costrutto API
Sezione intitolata “Migra il Costrutto API”L’applicazione PDK aveva un costrutto in constructs/apis/myapi.ts
che istanziava un costrutto CDK generato da Type Safe API dal modello Smithy.
Oltre a questo, dato che il progetto PDK utilizzava il trait @handler
, venivano generati anche costrutti CDK per le funzioni lambda.
Come Type Safe API, il Plugin Nx per AWS fornisce type-safety per le integrazioni basate sul modello Smithy, ma in modo più semplice e flessibile. Invece di generare un intero costrutto CDK a tempo di build, viene generato solo un minimo “metadata” che il file packages/common/constructs/src/app/apis/api.ts
utilizza in modo generico. Puoi saperne di più nell’guida del generatore ts#smithy-api
.
Segui questi passaggi:
-
Istanzia il costrutto
Api
inapplication-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 qui l’uso di
Api.defaultIntegrations(this).build()
: il comportamento predefinito è creare una funzione lambda per ogni operazione nella nostra API, come avveniva inmyapi.ts
. -
Concedi i permessi alle funzioni lambda per accedere alla tabella DynamoDB.
Nell’applicazione PDK,
DatabaseConstruct
veniva passato aMyApi
, che gestiva l’aggiunta dei permessi ai vari costrutti funzione. Ora lo faremo direttamente inapplication-stack.ts
accedendo alla proprietà type-safeintegrations
del costruttoApi
:stacks/application-stack.ts // Concedi accesso limitato alle lambda per DynamodatabaseConstruct.shoppingListTable.grantReadData(api.integrations.getShoppingLists.handler,);[api.integrations.putShoppingList.handler,api.integrations.deleteShoppingList.handler,].forEach((f) => databaseConstruct.shoppingListTable.grantWriteData(f)); -
Concedi ai utenti autenticati i permessi per invocare l’API.
Nell’applicazione PDK,
myapi.ts
concedeva permessi IAM per invocare l’API. Facciamo l’equivalente inapplication-stack.ts
:stacks/application-stack.ts api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
Migra il Costrutto Website
Sezione intitolata “Migra il Costrutto Website”Infine, aggiungiamo il costrutto Website
da packages/common/constructs/src/app/static-websites/website.ts
a application-stack.ts
, poiché è l’equivalente del packages/infra/src/constructs/websites/website.ts
dell’applicazione 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 che non passiamo identity o API al website: la configurazione runtime è gestita internamente dai costrutti del Plugin Nx per AWS, dove UserIdentity
e Api
registrano i valori necessari, e Website
li distribuisce in /runtime-config.json
sul sito statico.
Ora compiliamo il progetto dopo aver migrato tutte le parti rilevanti.
pnpm nx run-many --target build
yarn nx run-many --target build
npx nx run-many --target build
bunx nx run-many --target build
Ora che abbiamo la nostra codebase completamente migrata, possiamo procedere con la distribuzione. A questo punto abbiamo due strade percorribili.
Risorse Completamente Nuove (Semplice)
Sezione intitolata “Risorse Completamente Nuove (Semplice)”L’approccio più semplice è trattare questa come un’applicazione completamente nuova, il che significa che “ricominceremo” con una nuova tabella DynamoDB e un nuovo User Pool Cognito - perdendo tutti gli utenti e le loro liste della spesa. Per questo approccio, basta:
-
Elimina la tabella DynamoDB denominata
shopping_list
-
Distribuisci la nuova applicazione:
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 abbiamo finito! 🎉
Migrare le Risorse Esistenti con Stato Senza Interruzioni (Più Complesso)
Sezione intitolata “Migrare le Risorse Esistenti con Stato Senza Interruzioni (Più Complesso)”Nella realtà, è più probabile che tu voglia migrare le risorse AWS esistenti in modo che siano gestite dalla nuova codebase, evitando nel contempo qualsiasi interruzione del servizio per i clienti.
Per la nostra applicazione delle liste della spesa, le risorse con stato che ci interessano sono la tabella DynamoDB che contiene le liste della spesa degli utenti e l’User Pool che contiene i dettagli di tutti gli utenti registrati. Il nostro piano ad alto livello sarà mantenere queste due risorse chiave e spostarle in modo che siano gestite dal nuovo stack, per poi aggiornare il DNS per puntare al nuovo sito web (e all’API se esposta ai clienti).
-
Aggiorna la nuova applicazione per riferirsi alle risorse esistenti che desideri mantenere.
Per l’applicazione delle liste della spesa, facciamo questo per la tabella DynamoDB
constructs/database.ts this.shoppingListTable = new Table(this, 'ShoppingList', {...this.shoppingListTable = Table.fromTableName(this,'ShoppingList','shopping_list',);E per l’User Pool Cognito
packages/common/constructs/src/core/user-identity.ts this.userPool = this.createUserPool();this.userPool = UserPool.fromUserPoolId(this,'UserPool','<your-user-pool-id>',); -
Compila e distribuisci la nuova applicazione:
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/*Ora abbiamo la nuova applicazione attiva che fa riferimento alle risorse esistenti, ma non ancora in produzione.
-
Esegui test di integrazione completi per verificare che la nuova applicazione funzioni come previsto. Per l’applicazione delle liste della spesa, carica il sito web e verifica di poter accedere, creare, visualizzare, modificare ed eliminare liste.
-
Ripristina le modifiche che fanno riferimento alle risorse esistenti nella nuova applicazione, ma non distribuirle ancora.
constructs/database.ts this.shoppingListTable = new Table(this, 'ShoppingList', {...this.shoppingListTable = Table.fromTableName(this,'ShoppingList','shopping_list',);E per l’User Pool Cognito
packages/common/constructs/src/core/user-identity.ts this.userPool = this.createUserPool();this.userPool = UserPool.fromUserPoolId(this,'UserPool','<your-user-pool-id>',);E poi esegui una compilazione
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
nella cartellapackages/infra
della nuova applicazione per vedere quali risorse verranno proposte per l’importazione.Nuova Applicazione cd packages/infrapnpm exec cdk import shopping-list-infra-sandbox/Application --forceProcedi attraverso le richieste premendo invio. L’importazione fallirà perché le risorse sono gestite da un altro stack - è previsto, abbiamo fatto questo passaggio solo per confermare quali risorse dovremo mantenere. Vedrai un output simile a questo:
Terminal window shopping-list-infra-sandbox/Application/ApplicationUserIdentity/UserPool/smsRole/Resource (AWS::IAM::Role): inserisci RoleName (vuoto per saltare)shopping-list-infra-sandbox/Application/ApplicationUserIdentity/UserPool/Resource (AWS::Cognito::UserPool): inserisci UserPoolId (vuoto per saltare)shopping-list-infra-sandbox/Application/Database/ShoppingList/Resource (AWS::DynamoDB::Table): importa con TableName=shopping_list (y/n) yQuesto ci dice che in realtà ci sono 3 risorse da importare nel nuovo stack.
-
Aggiorna il tuo vecchio progetto PDK per impostare
RemovalPolicy
suRETAIN
per le risorse individuate nel passaggio precedente. Al momento della stesura questa è l’impostazione predefinita sia per l’User Pool che per la tabella DynamoDB, ma dobbiamo aggiornarla per il ruolo SMS individuato sopra: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); -
Distribuisci il progetto PDK per applicare le politiche di rimozione
Applicazione PDK cd packages/infranpx projen deploy -
Controlla la console CloudFormation e registra i valori richiesti nel passaggio
cdk import
precedente- L’ID dell’User Pool, es.
us-west-2_XXXXX
- Il nome del ruolo SMS, es.
infra-sandbox-UserIdentityUserPoolsmsRoleXXXXXX
- L’ID dell’User Pool, es.
-
Aggiorna il progetto PDK per riferirsi alle risorse esistenti invece di crearne di nuove
constructs/database.ts this.shoppingListTable = new Table(this, 'ShoppingList', {...this.shoppingListTable = Table.fromTableName(this,'ShoppingList','shopping_list',);E per l’User Pool Cognito
application-stack.ts const userPool = UserPool.fromUserPoolId(this,'UserPool','<your-user-pool-id>',);const userIdentity = new UserIdentity(this, `${id}UserIdentity`, {// Il costrutto PDK accetta UserPool non IUserPool, ma funziona comunque!userPool: userPool as any,}); -
Distribuisci nuovamente il progetto PDK, questo significa che le risorse non saranno più gestite dallo stack CloudFormation del progetto PDK.
Applicazione PDK cd packages/infranpx projen deploy -
Ora che le risorse non sono gestite, possiamo eseguire
cdk import
nella nuova applicazione per completare l’importazione:Nuova Applicazione cd packages/infrapnpm exec cdk import shopping-list-infra-sandbox/Application --forceInserisci i valori quando richiesto, l’importazione dovrebbe completarsi con successo.
-
Distribuisci nuovamente la nuova applicazione per assicurarti che eventuali modifiche a queste risorse esistenti (ora gestite dal nuovo stack) vengano applicate:
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/* -
Esegui nuovamente un test completo della nuova applicazione
-
Aggiorna i record DNS per puntare al nuovo sito web (e all’API se necessario).
Raccomandiamo un approccio graduale usando il Routing Ponderato di Route53, dove inizialmente una frazione delle richieste viene indirizzata alla nuova applicazione. Monitorando le metriche, puoi aumentare gradualmente il peso per la nuova applicazione finché tutto il traffico non viene indirizzato lì.
Se non hai DNS e hai usato i domini auto-generati per sito web e API, puoi considerare il proxy delle richieste (es. tramite un origine HTTP CloudFront o integrazioni HTTP API Gateway).
-
Monitora le metriche dell’applicazione PDK per assicurarti che non ci sia traffico residuo, infine elimina il vecchio stack CloudFormation:
Terminal window cd packages/infranpx projen destroy
È stato piuttosto articolato, ma abbiamo migrato gli utenti in modo trasparente alla nuova applicazione! 🎉🎉🎉
Ora abbiamo i nuovi vantaggi di Nx Plugin for AWS rispetto a PDK:
- Build più veloci
- Supporto allo sviluppo locale delle API
- Una codebase amichevole per il vibe-coding (prova il nostro server MCP!)
- Codice client/server type-safe più intuitivo
- E altro ancora!
Domande Frequenti
Sezione intitolata “Domande Frequenti”Questa sezione fornisce indicazioni per funzionalità di PDK non coperte dall’esempio di migrazione sopra.
Come regola generale quando si passa da PDK, consigliamo di iniziare qualsiasi progetto con un Workspace Nx, date le sue somiglianze con il Monorepo PDK. Consigliamo inoltre di utilizzare i nostri generatori come primitive su cui costruire nuovi tipi.
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
Sezione intitolata “CDK Graph”CDK Graph costruisce grafici delle tue risorse CDK connesse e fornisce due plugin:
Plugin Diagramma
Sezione intitolata “Plugin Diagramma”Il CDK Graph Diagram Plugin genera diagrammi dell’architettura AWS dalla tua infrastruttura CDK.
Un’alternativa valida con approccio deterministico simile è CDK-Dia.
Con i progressi nell’AI generativa, molti modelli di base sono in grado di creare diagrammi di alta qualità dalla tua infrastruttura CDK. Consigliamo di provare AWS Diagram MCP Server. Consulta questo blog post per una guida passo passo.
Plugin Threat Composer
Sezione intitolata “Plugin Threat Composer”Il CDK Graph Threat Composer Plugin genera un modello di minaccia iniziale per Threat Composer dal tuo codice CDK.
Questo plugin funziona semplicemente filtrando un modello di minaccia base contenente minacce di esempio, selezionando quelle rilevanti in base alle risorse utilizzate dal tuo stack.
Se sei interessato a queste minacce di esempio specifiche, puoi copiare e filtrare il modello base, o usarlo come contesto per far generare un modello simile da un modello di AI generativa.
AWS Arch
Sezione intitolata “AWS Arch”AWS Arch forniva le mappature tra le risorse di CloudFormation e le relative icone architetturali associate per CDK Graph sopra menzionato.
Fai riferimento alla pagina delle icone architetturali AWS per le risorse relative alle icone. Diagrams fornisce anche un modo per creare diagrammi come codice.
Se stavi utilizzando direttamente questo progetto, valuta di effettuare un fork del progetto e prendertene carico!
Pipeline
Sezione intitolata “Pipeline”Il PDK forniva un PDKPipelineProject
che configurava un progetto di infrastruttura CDK e utilizzava un costrutto CDK che incapsulava alcune risorse delle CDK Pipelines.
Per migrare da questo approccio, puoi utilizzare direttamente i costrutti delle CDK Pipelines. Tuttavia, nella pratica è spesso più semplice utilizzare soluzioni come GitHub Actions o GitLab CI/CD, dove si definiscono CDK Stages e si esegue direttamente il comando di deploy per lo stage appropriato.
PDK Nag
Sezione intitolata “PDK Nag”PDK Nag incapsula CDK Nag e fornisce una serie di regole specifiche per la creazione di prototipi.
Per migrare da PDK Nag, utilizza direttamente CDK Nag. Se hai bisogno dello stesso set di regole, puoi creare un tuo “pack” personale seguendo la documentazione qui disponibile.
Type Safe API
Sezione intitolata “Type Safe API”I componenti più comunemente utilizzati da Type Safe API sono coperti nell’esempio di migrazione sopra riportato, tuttavia esistono altre funzionalità per cui i dettagli di migrazione sono descritti di seguito.
API Modellate con OpenAPI
Sezione intitolata “API Modellate con OpenAPI”L’Nx Plugin per AWS supporta API modellate in Smithy, ma non quelle modellate direttamente in OpenAPI. Il generatore ts#smithy-api
è un buon punto di partenza che puoi poi modificare. Puoi definire la tua specifica OpenAPI nella cartella src
del progetto model
invece di Smithy, e modificare il build.Dockerfile
per utilizzare il tuo strumento di generazione codice preferito per client/server se non disponibile su NPM. Se gli strumenti desiderati sono su NPM, puoi semplicemente installarli come dipendenze dev nel tuo workspace Nx e richiamarli direttamente come target di build Nx.
Backend
Sezione intitolata “Backend”Per backend type-safe modellati in OpenAPI, puoi considerare l’uso di uno dei Server Generator di OpenAPI Generator. Questi non generano codice direttamente per AWS Lambda, ma puoi utilizzare AWS Lambda Web Adapter come ponte per molti di essi.
Per client TypeScript, puoi utilizzare i generatori ts#react-website
e api-connection
con un esempio ts#smithy-api
per vedere come i client vengono generati e integrati con un sito web. Questo configura target di build che generano client invocando i nostri generatori open-api#ts-client
o open-api#ts-hooks
. Puoi usare questi generatori puntandoli alla tua specifica OpenAPI.
Per altri linguaggi, puoi verificare se alcuni dei Client Generator di OpenAPI Generator soddisfano le tue esigenze.
Puoi anche costruire un generatore personalizzato utilizzando il generatore ts#nx-generator
. Consulta la documentazione di quel generatore per dettagli su come generare codice da OpenAPI. Puoi usare i template dell’Nx Plugin per AWS come punto di partenza. Puoi anche ispirarti ai template del codice PDK, notando che la struttura dati su cui operano i template è leggermente diversa rispetto all’Nx Plugin per AWS.
API Modellate con TypeSpec
Sezione intitolata “API Modellate con TypeSpec”Per TypeSpec, vale quanto riportato nella sezione precedente su OpenAPI. Puoi iniziare generando un ts#smithy-api
, installare il compilatore TypeSpec e i pacchetti OpenAPI nel tuo workspace Nx, e aggiornare il target compile
del progetto model per eseguire tsp compile
invece, assicurandoti che produca una specifica OpenAPI nella cartella dist
.
Backend
Sezione intitolata “Backend”L’approccio raccomandato è utilizzare il TypeSpec HTTP Server generator per JavaScript per generare il codice server, poiché opera direttamente sul modello TypeSpec.
Puoi usare AWS Lambda Web Adapter per eseguire il server generato su AWS Lambda.
Puoi anche utilizzare una qualsiasi delle opzioni OpenAPI sopra menzionate.
TypeSpec ha i suoi generatori di codice per client in tutti e tre i linguaggi supportati da Type Safe API:
La sezione OpenAPI sopra riportata si applica anche qui poiché TypeSpec può compilare in OpenAPI.
API Modellate con Smithy
Sezione intitolata “API Modellate con Smithy”L’esempio di migrazione sopra descritto illustra la migrazione all’uso del generatore ts#smithy-api
. Questa sezione copre le opzioni per backend e client in Python e Java.
Backend
Sezione intitolata “Backend”Il generatore Smithy per Java. Questo include un generatore di server Java e un adapter per eseguire il server Java generato su AWS Lambda.
Smithy non ha un generatore di server per Python, quindi dovrai passare attraverso OpenAPI. Fai riferimento alla sezione precedente sulle API Modellate con OpenAPI per opzioni possibili.
Il generatore Smithy per Java. Questo include un generatore di client Java.
Per client Python, puoi consultare Smithy Python.
Per TypeScript, consulta Smithy TypeScript, o usa lo stesso approccio adottato in ts#smithy-api
passando attraverso OpenAPI (abbiamo optato per questa soluzione per garantire coerenza tra API tRPC, FastAPI e Smithy tramite hook TanStack Query).
Smithy Shape Library
Sezione intitolata “Smithy Shape Library”Type Safe API forniva un tipo di progetto Projen chiamato SmithyShapeLibraryProject
che configurava un progetto contenente modelli Smithy riutilizzabili da più API basate su Smithy.
Il modo più diretto per ottenere ciò è il seguente:
Creare una Shape Library
Sezione intitolata “Creare una Shape Library”-
Crea la tua shape library usando il generatore
smithy#project
:- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - smithy#project
- Compila i parametri richiesti
- Clicca su
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#projectPuoi anche eseguire una prova per vedere quali file verrebbero modificati
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-runSpecifica un nome qualsiasi per l’opzione
serviceName
, poiché rimuoveremo la shapeservice
. -
Sostituisci il modello predefinito in
src
con le shape che desideri definire -
Aggiorna
smithy-build.json
rimuovendo iplugins
e le dipendenze Maven non utilizzate -
Sostituisci
build.Dockerfile
con passaggi di build minimi:build.Dockerfile FROM public.ecr.aws/docker/library/node:24 AS builder# Directory di outputRUN mkdir /out# Installa 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# Copia i file del progettoCOPY smithy-build.json .COPY src src# Build Smithy con cache MavenRUN --mount=type=cache,target=/root/.m2/repository,id=maven-cache \smithy buildRUN cp -r build/* /out/# Esporta la directory /outFROM scratch AS exportCOPY --from=builder /out /
Utilizzare la Shape Library
Sezione intitolata “Utilizzare la Shape Library”Nei tuoi progetti di modello di servizio, apporta le seguenti modifiche per utilizzare la shape library:
-
Aggiorna il target
compile
inproject.json
per aggiungere il workspace come contesto di build e una dipendenza sul targetbuild
della shape libraryproject.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"]} -
Aggiorna
build.Dockerfile
per copiare la directorysrc
dalla tua shape library. Ad esempio, assumendo che la shape library sia inpackages/shapes
:build.Dockerfile # Copia i file del progettoCOPY smithy-build.json .COPY src srcCOPY --from=workspace packages/shapes/src shapes -
Aggiorna
smithy-build.json
per aggiungere la directory shapes alle suesources
:smithy-build.json {"version": "1.0","sources": ["src/", "shapes/"],"plugins": {...}
Interceptors
Sezione intitolata “Interceptors”Type Safe API forniva i seguenti interceptors predefiniti:
- Interceptors di logging, tracing e metriche utilizzando Powertools for AWS Lambda
- Interceptor try-catch per gestire eccezioni non catturate
- Interceptor CORS per restituire header CORS
Il generatore ts#smithy-api
strumenta logging, tracing e metriche con Powertools for AWS Lambda usando Middy. Il comportamento dell’interceptor try-catch è integrato nello Smithy TypeScript SSDK, e gli header CORS vengono aggiunti in handler.ts
.
Per interceptors di logging, tracing e metriche in qualsiasi linguaggio, usa direttamente Powertools for AWS Lambda.
Per migrare interceptors personalizzati, raccomandiamo l’uso delle seguenti librerie:
- TypeScript - Middy
- Python - Powertools for AWS Lambda Middleware Factory
- Java - Strumenta metodi prima/dopo la tua business logic usando aws-lambda-java-libs per un approccio semplice, o considera AspectJ per costruire middleware come annotazioni.
Generazione Documentazione
Sezione intitolata “Generazione Documentazione”Type Safe API forniva generazione di documentazione usando Redocly CLI. Questo è facilmente aggiungibile a un progetto esistente dopo la migrazione.
-
Installa 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 -
Aggiungi un target di generazione documentazione al tuo progetto
model
usandoredocly build-docs
, ad esempio: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"]}}
Puoi anche considerare i documentation generator di OpenAPI Generator.
Mock Integrations
Sezione intitolata “Mock Integrations”Type Safe API generava mock per te all’interno del suo pacchetto infrastrutturale.
Puoi passare a JSON Schema Faker che può creare dati mock basati su JSON Schema. Questo può lavorare direttamente su una specifica OpenAPI, e ha una CLI che puoi eseguire come parte della build del progetto model
.
Puoi aggiornare la tua infrastruttura CDK per leggere il file JSON generato da JSON Schema Faker, e restituire l’appropriata MockIntegration
di API Gateway per un’integrazione, basata su metadata.gen.ts
(assumendo che tu abbia usato il generatore ts#smithy-api
).
Backend in Linguaggi Misti
Sezione intitolata “Backend in Linguaggi Misti”Type Safe API supportava l’implementazione di API con un mix di linguaggi nel backend. Questo può essere ottenuto fornendo “override” alle integrazioni quando istanzi il tuo costrutto API in 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(),});
Dovrai “stubare” il tuo service/router per far compilare il servizio se usi ts#smithy-api
e l’SDK Server TypeScript, ad esempio:
export const Service: ApiService<ServiceContext> = { ... Echo: () => { throw new Error(`Not Implemented`); },};
Validazione Input
Sezione intitolata “Validazione Input”Type Safe API aggiungeva validazione nativa di API Gateway per i corpi delle richieste basata sulla tua specifica OpenAPI, poiché utilizzava il costrutto SpecRestApi
.
Con il generatore ts#smithy-api
, la validazione è eseguita dall’SDK Server stesso. Questo vale per la maggior parte dei generatori di server.
Se desideri implementare la validazione nativa di API Gateway, puoi farlo modificando packages/common/constructs/src/core/api/rest-api.ts
per leggere lo schema JSON rilevante per il corpo della richiesta di ogni operazione dalla tua specifica OpenAPI.
API WebSocket
Sezione intitolata “API WebSocket”Purtroppo non esiste un percorso di migrazione diretto per le API websocket di Type Safe API che utilizzano API Gateway e Lambda con sviluppo API guidato da modelli. Tuttavia, questa sezione della guida mira almeno a offrire qualche idea.
Considera l’uso di AsyncAPI per modellare la tua API invece di OpenAPI o TypeSpec, poiché è progettato per gestire API asincrone. Il Template NodeJS di AsyncAPI può generare un backend websocket Node che puoi ospitare su ECS, ad esempio.
Puoi anche considerare AppSync Events per l’infrastruttura, e usare Powertools. Questo blog post è una lettura consigliata!
Un’altra opzione è usare API GraphQL con websocket su AppSync, per cui abbiamo un GitHub issue su cui puoi votare! Consulta la guida per sviluppatori di AppSync per dettagli e link a progetti di esempio.
Puoi anche considerare lo sviluppo di generatori di codice personalizzati che interpretino le stesse estensioni vendor di Type Safe API. Fai riferimento alla sezione API Modellate con OpenAPI per dettagli sulla creazione di generatori di codice basati su OpenAPI. Puoi trovare i template che Type Safe API utilizza per gli handler Lambda di API Gateway Websocket qui, e il client qui.
Puoi anche considerare la migrazione al generatore ts#trpc-api
per usare tRPC. Al momento non supportiamo ancora sottoscrizioni/streaming, ma se ne hai bisogno aggiungi un +1 al nostro GitHub issue.
Smithy è agnostico rispetto al protocollo, ma non supporta ancora il protocollo Websocket, fai riferimento a questo GitHub issue.
Infrastruttura in Python o Java
Sezione intitolata “Infrastruttura in Python o Java”PDK supporta infrastrutture CDK scritte in Python e Java. Al momento della stesura di questo documento, non forniamo supporto per questo nel Plugin Nx per AWS.
La soluzione consigliata è migrare la tua infrastruttura CDK a TypeScript oppure utilizzare i nostri generatori e migrare il pacchetto di costrutti comuni al linguaggio desiderato. Puoi utilizzare l’Intelligenza Artificiale Generativa per accelerare questo tipo di migrazioni, ad esempio Amazon Q CLI. Un agente IA può iterare sulla migrazione finché i modelli CloudFormation sintetizzati non risultano identici.
Lo stesso vale per l’infrastruttura generata da Type Safe API in Python o Java: puoi tradurre il costrutto generico rest-api.ts
dal pacchetto comune e implementare il tuo generatore di metadati semplificato per il linguaggio target (fai riferimento alla sezione API Modellate con OpenAPI).
Puoi utilizzare il generatore py#project
come base per progetti Python a cui aggiungere il tuo codice CDK (spostando il file cdk.json
e aggiungendo target rilevanti). Per progetti Java, puoi usare il plugin @nx/gradle
di Nx oppure @jnxplus/nx-maven
per Maven.
Utilizzo di Projen
Sezione intitolata “Utilizzo di Projen”Il PDK è stato costruito su Projen. Projen e gli Nx Generators presentano differenze piuttosto fondamentali, pertanto sebbene tecnicamente sia possibile combinarli, ciò rappresenta probabilmente un anti-pattern. Projen gestisce i file di progetto come codice in modo che non possano essere modificati direttamente, mentre i generatori Nx forniscono i file di progetto una volta e successivamente il codice può essere liberamente modificato.
Se desideri continuare a utilizzare Projen, puoi implementare autonomamente i tipi di progetto Projen che ti interessano. Per seguire gli schemi del Plugin Nx per AWS, puoi eseguire i nostri generatori o esaminare il loro codice sorgente su GitHub per comprendere come sono costruiti i tipi di progetto desiderati, quindi implementare le parti rilevanti utilizzando i primitivi di Projen.