Sviluppo remoto su AWS: da Cloud9 a VS Code
20 Novembre 2024 - 2 min. read
Alessio Gandini
Cloud-native Development Line Manager
query listUsers{ listUsers( limit: 2 ) { items { user_id, email, } } }Questo tornerà una lista di utenti (è gestita anche la paginazione tramite un nextToken).
{ "data": { "listUsers": { "items": [ { "user_id": "B2EF3212E32F423E", "email": "test.graph@besharp.it" }, { "user_id": "A2EF4512E32F45RK", "email": "test.full@besharp.it" } ], "nextToken": "tyJ3ZXJzaW9uIjoyLCJ0b2tl" } } }Ora vogliamo creare un nuovo utente:Request:
mutation createUser { createUser( input: { email: "test@besharp.it" } ) { user_id, email } }Questa mutazione creerà il nuovo utente o restituirà un'eccezione se già esiste.Uno dei vantaggi di GraphQL è che il frontend può ottenere esattamente le informazioni di cui ha bisogno minimizzando il numero di chiamate API e il peso delle risposte: ad esempio se vogliamo solo il nome di un utente e non ci importa dell'email:Request:
query getUser{ getUser(user_id: "A2EF4512E32F45RK") { name } }Response:
{ "data": { "getUser": { "name": "Test Full" } } }Se invece dovessimo avere anche bisogno del suo user id:
query getUser{ getUser(user_id: "A2EF4512E32F45RK") { user_id email name } }Response:
{ "data": { "getUser": { "user_id": "A2EF4512E32F45RK" "email": "test.full@besharp.it", "name": "Test Full" } } }Inoltre, GraphQL è stato sviluppato per consentire al frontend di recuperare dati da fonti eterogenee. Ad esempio, pensiamo ad un'entità ‘utente’ che ha anche altre proprietà oltre a quelle finora considerate: l'utente potrebbe essere memorizzato in un database relazionale (ad esempio Aurora Serverless Postgres) ma potrebbe avere un campo GraphQL che descrive la sua località geografica (con relative proprietà) che è memorizzata in DynamoDB, inoltre a livello di GraphQL potremmo aggiungere un campo "Meteo locale" alla località che recupera i dati delle previsioni meteorologiche locali da un'API di terze parti e che è popolato a runtime quando viene eseguita una query GetUser o ListUser.Mentre le funzionalità del linguaggio GraphQL sono eccezionali, è sempre stato molto difficile configurare un back-end GraphQL utilizzando il paradigma serverless prima del rilascio di AWS AppSync. Questo nuovo servizio AWS ha infatti lo stesso ruolo di API Gateway per un'applicazione RESTful: riceve le chiamate API, controlla l'Autenticazione - Autorizzazione e le inoltra alla Lambda corretta o ad altri servizi di back-end. AppSync inoltre fa ancora di più: è possibile scrivere un backend personalizzato minimale utilizzando i modelli VTL (Apache Velocity) per collegare query e mutazioni direttamente alle origini dati (DynamoDB, Aurora Serverless, AWS ElasticSearch, API di terze parti). Per query e mutazioni più complicate è ovviamente necessario un backend ‘reale’ (ad es. per la crittografia custom per un campo del database). In questo caso, AppSync, proprio come Api Gateway, può richiamare una funzione Lambda per eseguire le operazioni necessarie e quindi utilizzare la risposta (o le risposte) per ricostruire il JSON finale che deve essere restituito al client.[caption id="attachment_1328" align="aligncenter" width="1600"] Sketch of a typical AWS GraphQL (AppSync) serverless application[/caption]La bellezza di questo approccio è che possiamo configurare AppSync per chiamare una funzione lambda ogni volta che deve popolare un campo particolare di una data entità oppure popolare una intera entità. Quindi se eseguiamo una query di list, per ogni risultato restituito, una Lambda dedicata può essere eseguita per valutare un campo specifico, ad esempio il campo delle previsioni del tempo personalizzate nell'esempio sopra, mentre gli altri campi proverranno direttamente dai database. Questo è il motivo per cui la risposta sarà sempre molto rapida anche per le query che richiedono un numero elevato di operazioni personalizzate, dato che tali operazioni verranno sempre parallelizzate. Va comunque tenuto conto del fatto che, poiché le funzioni lambda sono fatturate per numero di esecuzione e per tempo di esecuzione totale, le query mal progettate possono comportare costi di AWS elevati ed imprevisti, perciò, in generale, è sempre utile impiegare il tempo necessario per pianificare accuratamente la corretta query e l'utilizzo previsto delle Lambda.Sfortunatamente il prezzo da pagare per l'elevata configurabilità e flessibilità di AppSync è la complessità e la “verbosità” della sua configurazione: anche per un progetto molto semplice con una manciata di entità e una o due tabelle Dynamo è infatti necessario un file di schema lungo e dettagliato che descrive tutte le entità, le loro eventuali relazioni e come devono essere eseguite le mutazioni e le query. Inoltre, per ogni query e mutazione devono essere creati 2 file vtl (Velocity), uno per l'analisi della richiesta e uno per l'analisi dell'output. Se si desidera utilizzare una funzione lambda o una catena di funzioni lambda (risolutori di pipeline), per popolare o recuperare un campo o un'entità, la complessità della configurazione aumenta in modo significativo così come lo sforzo necessario per mantenere e testare il progetto. Inoltre, creare e aggiornare l’infrastructure as Code (IaC) di un insieme di configurazioni così ampio, fortemente integrato e spesso in rapida evoluzione in un modo rapido, ripetibile e affidabile può essere un compito estremamente complesso.Per semplificare la gestione del progetto è quindi necessario l'utilizzo di un framework per automatizzare la creazione delle parti banali e ripetitive delle configurazioni di AppSync. Il framework Serverless (molto diffuso) ha un plug-in AppSync dedicato, tuttavia il suo utilizzo non è molto più semplice o più veloce della creazione manuale del CloudFormation nativo per AppSync. Ciò che abbiamo scoperto dopo svariati test è che una miscela di CloudFormation/Troposphere nativi con il framework AWS Amplify può risolvere il problema. Amplify è una piattaforma di sviluppo per creare rapidamente applicazioni Web e Mobile utilizzando i servizi AWS Serverless (Lambda, API Gateway, Appsync, Dynamo, SQS, ecc.) in modo trasparente. Sebbene sia spesso troppo limitato per un'applicazione complessa del mondo reale e, in generale, più che altro pensato per sviluppatori con conoscenze di AWS molto limitate che vogliono configurare rapidamente un'applicazione funzionante sfruttando i più moderni servizi serverless, la sua integrazione con AppSync è molto ben fatta e consente agli sviluppatori di configurare rapidamente un'integrazione GraphQL basata su DynamoDB (o Aurora) senza tutte le complessità e la verbosità descritte precedentemente. Inoltre, rende davvero semplice e facile sfruttare AWS Cognito per l’autenticazione e l'autorizzazione. E se Cognito da solo non dovesse soddisfare le vostre esigenze, è anche possibile usare la IAM authentication, tramite i Cognito Identity Pools.Ecco come abbiamo creato un back-end AppSync con Amplify e CloudFormation in modo rapido, affidabile e facilmente gestibile: prima di tutto è necessario installare Amplify Cli come descritto nella documentazione di Amplify. Dopo aver fatto ciò, è possibile seguire la guida e continuare a la creazione di un progetto Amplify in una cartella dedicata tramite i comandi:
amplify configure amplify init amplify add api -> choose GraphQLTutti i comandi sono interattivi: scegli le opzioni più adatte alle tue esigenze! Dopo aver creato il progetto GraphQL api sarà necessario modificare il file schema.graphql nella cartella api/backend/: questo è l'unico file che è necessario modificare se l'applicazione non ha bisogno di integrazioni complicate! La sintassi di questo file è quella del linguaggio GraphQL con alcune annotazioni personalizzate di Amplify. Utilizzando queste annotazioni è possibile definire quali entità sone tabella Dynamo, creare Global Secondary Indexes, connessioni ad altre tabelle, campi personalizzati che usano le lambda come data source, impostare il livello di autorizzazione per ciascuna entità usando il token JWT di Cognito e molto altro ancora!Quando si è soddisfatti della propria configurazione di GraphQL è possibile eseguire:
amplify pushE Amplify creerà ed eseguirà i template CloudFormation per creare o aggiornare AppSync!Nonostante le varie opzioni disponibili in Amplify GraphQL nel caso di progetti complessi, probabilmente sarà necessario usare alcune funzionalità di AppSync non presenti di default tuttavia questo non è un problema: quando si esegue amplify push viene generata una cartella di build (.gitignored) e contiene la versione corrente di cloudFormation, tuttavia esiste anche una cartella api/backend // stacks che contiene un cloudFormation json (CustomResources.json) senza risorse: è possibile aggiungere qui ulteriori risorse custom. È anche possibile usare Troposphere per creare questo file, sfruttando così le funzionalità avanzate di Python nella creazione del modello CloudFormation. Nel caso fossero anche necessari anche file VTL personalizzati è possibile aggiungerli alla cartella api/backend//resolvers.Dopo l'entusiasmo iniziale dei primi rilasci di solito iniziano tuttavia ad emergere i problemi legati all’uso di Amplify. Infatti è necessario mantenere vari componenti diversi: il file schema.graphql; la configurazione CloudFormation/Troposphere per le risorse personalizzate e i file VTL custom. Se si è scelto di usare Troposphere sarà anche necessario ricordare di eseguire lo script per creare i file yml di CloudFormation prima di eseguire amplify push.Sono perciò necessari ulteriori passaggi di automazione: la soluzione più elegante è certamente la creazione di una pipeline (AWS CodePipeline) per eseguire il deploy della nostra applicazione GraphQL. La Source della pipeline può essere il repository AWS CodeCommit del progetto Amplify, oppure qualsiasi altro repository git (GitHub e Bitbucket sono supportati di default mentre altre configurazioni git lo sono tramite integrazioni custom).Per far ciò iniziamo ripulendo tutto ciò che Amplify ha creato nel nostro account. Ricreeremo tutto da zero senza usare amplify push. Le risorse da eliminare sono gli stack Amplify di CloudFormation, i bucket S3 relativi all'amplificazione e l'applicazione Amplify nella console di AWS Amplify.Dopo aver pulito l’account AWS si può rimuovere tutto dal progetto, esclusa la cartella backend mentre il file team-provider-info.json dovrebbe essere riempito con un json vuoto (basta scrivere {}). Ora è possibile creare un file buildspec.yml nella directory principale che verrà utilizzato da AWS CodeBuild:
version: '0.2' phases: install: runtime-versions: python: 3.7 nodejs: 12 commands: - npm install -g @aws-amplify/cli - pip install -r requirements.txt build: commands: - echo "{ \"projectPath\": \"$(pwd)\", \"envName\": \"${ENVIRONMENT}\"}" > amplify/.config/local-env-info.json - amplify api gql-compileQuesti comandi creeranno i file di CloudFormation e i VTL nella cartella build senza eseguirli. A questo punto sarà solo necessario aggiungere al codebuild config la variabile ENVIRONMENT che identifica l'ambiente per cui stiamo eseguendo la build e utilizzare il file principale di CloudFormation nella cartella build come output di codebuild (build/cloudformation-template.json). Nel caso si stia usando Troposphere è anche necessario aggiungere uno step aggiuntivo per compilare Python in CloudFormation. Bisogna infine caricare gli altri modelli di CloudFormation e i file VTL in un bucket S3 di propria scelta.Il flusso è quello mostrato nell'immagine seguente:L'ultimo passaggio della CodePipeline consiste nell'eseguire il modello di CloudFormation (output di codebuild) in uno step di deploy dedicato. CloudFormation si occuperà di scaricare gli altri template da s3 e di eseguirli come stack nested.Ora hai il tuo progetto GraphQL personalizzato, configurabile e facilmente gestibile supportato da Appsync e Dynamo e deployato con Amplify e CloudFormation!Se ti è piaciuta questa soluzione e desideri avere ulteriori dettagli, non esitare a scrivere nei commenti o a contattarci!