Figura 3: Job a lunga esecuzione in un’applicazione web AWS: app basata su EC2 a sinistra, serverless a destra<\/figcaption><\/figure><\/div>\n\n\n\nInfine, spesso \u00e8 utile inviare una notifica direttamente al browser client per aggiornare lo stato dei componenti del frontend in risposta a eventi come come stato di esecuzione delle attivit\u00e0 in esecuzione, notificare all’utente azioni eseguite da altri utenti (ad esempio nei giochi online), recapitare messaggi e sempre pi\u00f9 spesso notificare agli utenti i cambiamenti di stato di dispositivi IoT. <\/p>\n\n\n\n
Notifiche in tempo reale<\/h2>\n\n\n\n Mentre nelle applicazioni classiche \u00e8 possibile utilizzare direttamente i websocket, anche tramite AWS ELB che supportano HTTP\/2, per le applicazioni serverless \u00e8 necessario sfruttare il supporto Websocket di AWS ApiGateway che \u00e8 anche supportato nativamente da diversi framework serverless, come Chalice. Quando una connessione WebSocket viene stabilita da un client, una lambda pu\u00f2 essere invocata dall’hook $connect<\/em> di ApiGateway e sar\u00e0 in grado di registrare l’id di connessione a un database, solitamente DynamoDB. Quando un utente si disconnette, viene invocato $disconnect<\/em> e ci\u00f2 consente alla nostra applicazione di eliminare il record dalla tabella delle connessioni. Sviluppare una logica per inviare notifiche \u00e8 piuttosto semplice: quando un messaggio deve essere consegnato dal backend a un utente, l’ApiGateway @connections POST Api viene invocato utilizzando gli id delle connessioni websocket aperte dell’utente e ApiGateway si occupa di inoltrare il messaggio nel canale websocket aperto.<\/p>\n\n\n\nFigura 4: Notifiche Websocket in tempo reale in un’applicazione Web AWS: app basata su EC2 a sinistra, serverless a destra <\/figcaption><\/figure>\n\n\n\nUn esempio del mondo reale<\/h2>\n\n\n\n Sebbene queste tecniche siano particolarmente utili per le applicazioni ad alto traffico, anche le app a basso traffico possono trarre grandi vantaggi dalla loro implementazione, in particolare quelle che gestiscono flussi di lavoro complessi. <\/p>\n\n\n\n
La seguente architettura di esempio \u00e8 una versione notevolmente semplificata dell’architettura che abbiamo effettivamente realizzato per un’applicazione che consente ad un cliente di gestire dinamicamente i bucket S3 e gli utenti IAM che vi accedono.<\/p>\n\n\n\n
Figura 5: Prima versione dell’infrastruttura applicativa di esempio<\/figcaption><\/figure><\/div>\n\n\n\nL’applicazione consente ai project manager del cliente di creare facilmente bucket S3 protetti in account AWS dedicati, specifici per progetto, isolati e hardenizzati. Una volta creati i bucket, \u00e8 anche possibile generare utenti IAM gestiti per accedervi. Le credenziali dell’utente IAM possono quindi essere condivise in modo sicuro con terze parti per consentire loro di scaricare e\/o caricare file da S3 utilizzando sistemi legacy on-premise, sfruttando le API di S3 o SFTP (AWS Transfer for SFTP). Utenti e Bucket possono essere facilmente aggiunti e rimossi da ciascun progetto e le autorizzazioni utente possono essere gestite tramite una semplice interfaccia utente.<\/p>\n\n\n\n
Per semplificare lo sviluppo del backend e allo stesso tempo rendere l’applicazione pi\u00f9 resiliente, abbiamo deciso di non configurare alcun database e utilizzare semplicemente il backend per creare, aggiornare e modificare dei modelli di Cloudformation che descrivono l\u2019intera infrastruttura AWS. In questo modo viene rafforzata la consistenza dell\u2019applicazione: ogni azione eseguita viene automaticamente registrata e, in caso di errore durante la creazione di una risorsa, i rollback vengono eseguiti automaticamente direttamente dal servizio Cloudformation.<\/p>\n\n\n\n
Tuttavia questo approccio presenta due svantaggi principali, uno per le operazioni LIST\/GET e uno per le operazioni CREATE\/UPDATE. <\/p>\n\n\n\n
Infatti, ogni volta che un utente finale elenca le risorse esistenti, il backend deve recuperare tutti i modelli di CloudFormation, analizzarli per elencare le risorse e infine restituire la risposta. Questo flusso deve essere eseguito almeno su un modello ogni volta che un utente esegue un’operazione LIST o GET. Per account con dozzine di bucket e utenti ci\u00f2 pu\u00f2 richiedere diversi secondi, rendendo l’esperienza dell’utente molto scadente e potenzialmente, in alcuni casi estremi, pu\u00f2 portare al superamento del limite di risposta di ApiGateway (30 secondi).<\/p>\n\n\n\n
Il secondo problema riguarda le operazioni di aggiornamento e si manifesta quando sono connessi pi\u00f9 clienti: se un gestore aggiorna un bucket o un utente, tutti gli altri gestori non potranno modificarlo fino al termine dell’esecuzione di Cloudformation, che in questo caso d’uso richiede solo pochi secondi. Tuttavia, gli altri utenti connessi non hanno modo di sapere quando un utente viene aggiornato e potrebbero tentare di modificarlo causando errori imprevisti che, sebbene innocui, rendono l’esperienza dell’utente poco fluida.<\/p>\n\n\n\n
Oltre a questi problemi c’\u00e8 anche un’ulteriore potenziale inconveniente: poich\u00e9 l’infrastruttura descritta \u00e8 “GET heavy” verso le API di CloudFormation, se un numero sufficientemente grande di gestori accede, il rate limit dell’API di CloudFormation potrebbe potenzialmente essere superato con conseguenti ulteriori rallentamenti (gli SDK AWS implementano l\u2019esponential backoff).<\/p>\n\n\n\n
Per risolvere tutti questi problemi, dopo l’MVP iniziale, abbiamo applicato il design pattern sopra descritto per rendere applicazione pronta per essere messa in produzione: abbiamo utilizzato DynamoDB per memorizzare nella cache lo stato di CloudFormation e abbiamo utilizzato l’integrazione di CloudFormation con SNS per aggiornare lo stato in tempo reale tramite connessioni Websocket (gestite da Api Gateway) verso i client degli utenti e allo stesso tempo per aggiornare la cache su dynamoDB con l’ultimo stato di Cloudformation, in modo che lo stato nel DB di cache rispecchi sempre lo stato di Cloudformation.<\/p>\n\n\n\n
Figura 6: versione finale dell’infrastruttura applicativa di esempio<\/figcaption><\/figure><\/div>\n\n\n\nQuesti accorgimenti hanno prodotto enormi miglioramenti nei tempi di risposta delle APIs del backend, che per le richieste GET\/LIST sono scesi costantemente al di sotto dei 200 ms. Inoltre, il tempo di attesa degli utenti dopo il lancio di un aggiornamento di CloudFormation risulta notevolmente diminuito e lo stato delle risorse \u00e8 coerente su tutti i client connessi.<\/p>\n\n\n\n
Il cliente \u00e8 stato pi\u00f9 che soddisfatto del risultato finale e un cliente felice \u00e8 sempre la migliore conferma di un lavoro ingegneristico ben fatto!<\/p>\n\n\n\n
Se hai domande sui modelli di progettazione per DynamoDB o su qualsiasi altro argomento riguardante (o non riguardante) questo articolo, non esitare a contattarci! Non vediamo l’ora di discuterne \ud83d\ude42<\/p>\n\n\n\n
Ci vediamo qui, su #Proud2beCloud<\/strong> tra 14 giorni per un nuovo articolo!<\/p>\n","protected":false},"excerpt":{"rendered":"Le infrastrutture Serverless offrono enormi vantaggi rispetto alle infrastrutture server “classiche”. Basti pensare ad un’applicazione Web serverless di base basata […]<\/p>\n","protected":false},"author":9,"featured_media":3347,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[481],"tags":[263,522,524,267],"yoast_head":"\n
Caching e long-jobs con notifiche in tempo reale in una Web App Serverless basata su AWS - Proud2beCloud Blog<\/title>\n \n \n \n \n \n \n \n \n \n \n \n \n\t \n\t \n\t \n \n \n \n \n \n \n\t \n\t \n\t \n