Le notifiche push sono una delle principali funzionalità che differenziano l’esperienza di fruizione di un servizio tramite applicazione mobile, rispetto all’uso di una piattaforma web. Infatti, tutt’ora (Settembre 2022) le Notifications API non sono ancora supportate da tutti i principali browser. Per l’app Nuvola – Tutore Studente è stato necessario sviluppare una infrastruttura di push notifications per consentire ai docenti di inviare notifiche ai genitori e/o agli studenti riguardo comunicazioni e prossimi eventi. In questo articolo, riassumiamo le principali decisioni tecnologiche e architetturali che abbiamo fatto per implementare l’infrastruttura a supporto di questa funzionalità.
Prima di discutere le scelte infrastrutturali dobbiamo capire lo scopo di questo servizio.
Una tipologia di utenti andrà a schedulare (con granularità al minuto) l’invio di notifiche push, più o meno personalizzate, a dispositivi mobili di uno o più utenti e con sistemi operativi Android e iOS. Gli stessi potranno aggiornare o cancellare le notifiche in programma, prima che vengano inviate. Giunti alla data ed ora programmata, viene recuperato l’elenco dei device appartenenti all’utente destinatario ed inviati.
In questo articolo discuteremo delle principali decisioni tecnologiche e architetturali prese per implementare l’infrastruttura per la schedulazione delle Push-Notifications ed il perchè di determinate scelte. Non ci soffermeremo sui dettagli implementativi di ogni singolo componente ma daremo una vista dall’alto, in modo che ognuno possa poi scegliere liberamente la logica nell’eseguire le operazioni nelle funzioni Lambda, il linguaggio (nel nostro caso usiamo Python) ed i dettagli in base ai propri gusti ed esigenze.
Infrastruttura
Questo microservizio, come altri di recente, è separato dal resto di Nuvola ed è quasi interamente composto da componenti AWS. Viene infatti utilizzato Firebase per l’invio di notifiche verso Android e la controparte iOS.
Per integrare Firebase alla nostra infrastruttura abbiamo ritenuto ottimale l’utilizzo di AWS SNS Mobile Push Notifications, tramite la registrazione di un SNS Platform Application per Android e iOS.
Di seguito lo schema infrastrutturale utilizzato, filtrato da tutti i servizi accessori (IAM Roles, Cloudwatch, EventBridge, etc).
Per semplicità l’infrastruttura intermedia di Nuvola, che si pone tra l’utente e questo servizio, è stata astratta come ‘user mobile device’.
Generalmente possiamo evidenziare 2 flussi diversi: gestione dei “device tokens” e “notifications”.
Il primo flusso (nel diagramma parte da “API devices tokens”) gestisce i Tokens generati da ogni device ed è necessario a Firebase al fine di indirizzare correttamente la notifica al giusto device. Ogni nuovo device che accede alla nostra applicazione verrà registrato associandogli un SNS Arn generato da SNS Platform Application.
Il secondo flusso (partendo da “API notifications”) rappresenta tutti gli step necessari per la gestione delle notifiche push, dalla creazione all’invio verso Firebase.
Per descrivere al meglio l’infrastruttura possiamo dividerla in 4 parti distinte, ognuna con un compito ben preciso:
- Gestione device tokens
- Gestione notifiche
- Cron check notifiche da inviare
- Creazione payload ed pubblicazione su SNS
Gestione device Tokens
Questa parte è composta da 4 servizi AWS:
- API Gateway
- Lambda
- DynamoDB
- SNS Mobile Push Notifications (Platform Application)
Lo scopo è quello di gestire i device tokens di ogni utente registrato all’applicazione.
Tramite API Gateway viene inviata la richiesta per la registrazione di un token, la quale verrà gestita da una funziona Lambda che contatterà SNS Mobile Platform Application al fine di ottenere l’Arn univoco per il device (nel caso in cui non sia già registrato). Le informazioni principali utente/device ed l’Arn appena generato verranno salvate in una tabella DynamoDB.
La logica e l’infrastruttura di questo step è deriva da un articolo nel blog AWS.
Gestione notifiche
La gestione delle notifiche si compone dei servizi:
- API Gateway
- Lambda
- DynamoDB
Questa classica struttura ha lo scopo di gestire la creazione, l’update e la cancellazione delle notifiche schedulate. La Lambda gestisce i metodi dell’API Gateway ed interloquisce con DynamoDB per il salvataggio o la rimozione degli item.
Ogni item nella tabella rappresenta una singola notifica push da inviare ad uno o più utenti ad una determinata data ed ora, con un determinato messaggio ed uno o più device di destinazione.
Come descritto poco fa, la funzione Lambda salva per ogni item della tabella in DynamoDB anche un campo contenente i destinatari della notifica, i quali vengono recuperati dalla tabella DynamoDB dei device tokens.
Questa parte dell’infrastruttura anche essendo molto semplice é tra le più delicate, soprattutto in caso di enormi quantità di traffico. Torneremo a parlare di questo e dei miglioramenti nei paragrafi “note implementative” e “Ampliamento e miglioramenti infrastruttura”.
Cron check notifiche da inviare
In questo caso troviamo i servizi:
- DynamoDB
- Lambda
- SQS
- EventBridge
La funzione Lambda viene invocata ogni minuto da un evento AWS EventBridge per controllare la presenza di notifiche da inviare nella tabella DynamoDB descritta nello step precedente.
La funzione andrà ad effettuare nella tabella una query tramite un indice secondario (Global Secondary Index), per raccogliere tutti gli item con schedulazione per il minuto precedente all’invocazione.
Una volta recuperate le notifiche da inviare vengono strutturate ed assemblate in blocchi da dieci per l’accodamento nella coda SQS. Grazie a questo ultimo servizio possiamo gestire come, quando e da chi verranno poi consumati i messaggi senza creare accoppiamenti diretti con altri componenti dell’infrastruttura.
Creazione payload ed pubblicazione su SNS
Il cuore di questo corpo, ma del resto di tutta l’infrastruttura, è SNS Mobile Push Notifications. Questo componente si preoccupa di comunicare con la nostra app registrata su Firebase, quindi evitare la gestione delle chiamate Rest verso le api pubbliche di Firebase.
Il nostro compito è quello di creare un payload compatibile con SNS e quindi con Firebase. La Lambda si occupa specificatamente di questo compito: riceve in input dalla coda SQS i dati recuperati da Dynamo e creare tanti payload quanti device di destinazione abbiamo, pubblicandoli su SNS, tramite l’ Arn univoco del device.
SNS ci astrae tutta la parte di pubblicazione verso Firebase ed i vari tentativi di retry, che avvengono in maniera asincrona.
Nota: Al momento della scrittura di questo articolo SNS utilizza la versione legacy delle API di Firebase, quindi il payload inviatogli deve essere formattato di conseguenza.
Note di implementazione
- Nel nostro caso la Rest API è privata. Questo ci permette, tramite un VPC Link, di raggiungerla senza esporla pubblicamente.
- Nella progettazione di un’integrazione tra API-Gateway e Lambda functions bisogna tenere conto del tempo che sarà impiegato dalla funzione per processare la richiesta. Infatti API-Gateway prevede un massimo di 29 secondi come Integration timeout. Nel caso in cui la funzione impiegasse più tempo del previsto (29 secondi), questa continuerebbe nell’esecuzione (a meno di un timeout della funzione stessa) mentre API-Gateway risponderà al client con un errore 500 “Timeout”. Quindi nel caso si preveda un tempo elevato di processazione si consiglia un ulteriore step tra i due servizi o configurazioni asincrone (ne parliamo nel paragrafo successivo).
- La funzione Lambda che controlla la presenza di nuove notifiche da inviare nella tabella DynamoDB ne potrebbe recuperare migliaia al minuto. Per velocizzare il processo di pubblicazione sulla coda SQS si consiglia di raggruppare i messaggi.
- Il tuning dell’invocazione Lambda da parte di SQS (pubblicazione su SNS) è essenziale per non esagerare troppo con i costi. I principali parametri da tenere in considerazione sono batch_size e max_batching_window.
Per gestire singolarmente eventuali errori della Lambda provocati da un singolo messaggio di un batch, si consiglia l’attivazione del parametro report_batch_item_failures. Tuttavia questo comporta una gestione particolare delle risposte che la Lambda deve fornire a SQS (risolvibile facilmente tramite il tool aws-lambda-powertools-python). - Consigliamo l’aggiunta di una Dead Letter Queue nel caso la coda SQS non riesca a consegnare correttamente i messaggi dopo il numero di tentativi impostati. Questa è utile sia per una questione di monitoraggio che di affidabilità del sistema (si può gestire il reinserimento in coda dei messaggi).
Codice infrastrutturale
L’infrastruttura è stata creata quasi interamente tramite AWS CDK versione 2.
Il Platform application di SNS Mobile push notifications non è possibile crearlo tramite CDK ed è quindi necessaria la creazione manuale tramite AWS web console. Ovviamente l’applicazione Firebase deve essere creata manualmente e precedentemente al SNS Platform application.
Per la scrittura del codice delle funzioni Lambda abbiamo ritenuto comodo l’utilizzo dei tools messi a disposizione da aws-lambda-powertools-python, da cui ad esempio abbiamo ottenuto diversi vantaggi per la gestione delle richieste da parte di API-Gateway. Esistono altre librerie che semplificano la gestione dei payload da parte di API-Gateway (Chalice ad esempio) ma questo risulta più snello (privo della CLI) e con l’utile astrazione dedicata alla gestione dell’integrazione tramite report_batch_item_failures con SQS.
Ampliamento e miglioramenti infrastruttura
Come per ogni infrastruttura AWS possono essere aggiunte una moltitudine di servizi aggiuntivi. Si pensi ad esempio a sistemi di sicurezza come WAF per API-Gateway pubbliche o il monitoraggio tramite AWS Lambda Insights.
Nel diagramma sopra ho voluto mostrare, nel limite della semplicità dello schema, i miglioramenti più interessanti che si potrebbero apportare:
- Nelle note di implementazione ho parlato del limite di 29 secondi per l’integrazione tra API-Gateway e Lambda e del problema che ne può scaturire. Per evitarlo ho individuato tre soluzioni:
- rendere l’integrazione tra i due servizi asincrona. Tuttavia dobbiamo sapere che di contro non potremo mai avere indietro una eventuale risposta dalla Lambda al termine dell’elaborazione.
- Utilizzare una Step-Function come intermediario che ha il compito di invocare la funzione Lambda, attendere fino al completamento dell’esecuzione e quindi prendere il risultato da comunicare tramite una chiamata ad un hook creato nel sistema backend.
- Con un altro topic SNS possiamo aggiungere tutti i canali di trasmissione delle notifiche che preferiamo. Infatti ci basta avere un parametro sender_type (ad esempio) in ogni item (nella tabella DynamoDB che ospita le notifiche) che indica il servizio tramite il quale spedire la notifica ed impostare il Message Filtering nel topic SNS. In questo modo i messaggi verranno smistati alla giusta coda SQS e di conseguenza alla giusta Lambda o altri servizi AWS.
- Come spesso capita a tutti noi quando ci registriamo ad un servizio o aggiungiamo un device ad un servizio a cui siamo già registrati, riceviamo una mail o un SMS di conferma. In SNS Mobile Platform Application possiamo specificare un topic SNS dove pubblicare non appena un device token è stato aggiunto o cancellato e di conseguenza far partire un un altro flusso per notificare l’utente e/o per statistiche e metriche.
- SNS Mobile Platform Application supporta il salvataggio di log di successo ed insuccesso delle chiamate POST verso Firebase. Tramite questi log possiamo far scattare delle operazioni tramite Metric e Subscription filter di CloudWatch logsGroup. Può essere utile al fine di statistiche o accodamento degli invii falliti.
Conclusione
In un percorso di migrazione ed espansione a micro servizi è stata costruita un’infrastruttura AWS per l’invio di notifiche push a device mobili, al cui centro troviamo AWS SNS Mobile Push Notifications.
Questa soluzione ci permette di decentralizzare il carico di lavoro e la gestione dell’invio, tramite Firebase, del messaggio all’utente finale. I principali servizi che abbiamo ritenuto opportuni, in base ai carichi di lavoro che ci aspettiamo, sono API-Gateway, Lambda, DynamoDB, SQS ed SNS. Tramite questi possiamo fornire, al team che si occupa dell’aggiunta del servizio push notifications in Nuvola, dei semplici metodi Rest senza preoccupazioni riguardo l’archiviazione e la gestione dei messaggi da inviare.
Cosa ne pensi di questa soluzione? Hai già adottato un flusso simile o ritieni ci siano soluzioni migliori? Facci sapere!