-
Notifications
You must be signed in to change notification settings - Fork 1
Home
Name | Matricola | Github | |
---|---|---|---|
Lorenzo Cereser | 209390 | lorenzo.cereser@studenti.unitn.it | lcereser6 |
Manuela Corte Pause | 209561 | manuela.cortepause@studenti.unitn.it | ManuelaCorte |
Riccardo Gennaro | 209272 | riccardo.gennaro@studenti.unitn.it | lallo-unitn |
Francesco Gentile | 213480 | francesco.gentile@studenti.unitn.it | FrancescoGentile |
Questa applicazione web ha come obiettivo il miglioramento dell'organizzazione e delle comunicazioni interne allo studentato Nest Trento.
L'app mette a disposizione dei residenti delle funzionalità per la prenotazione delle aule e per la visualizzazione delle notizie pubblicate dai club e dalla direzione della struttura.
Nota: nella documentazione delle API abbiamo fatto talvolta uso di specifiche OpenAPI che non sono ancora supportate da Apiary.
- Apiary: https://nextapp1.docs.apiary.io/
- Heroku:
- Product backlog: https://docs.google.com/spreadsheets/d/102tYKUlZ8sIwR-bB2-5kJoyR-t58lZUs3v4KLJqFwPY/edit?usp=sharing
Il frontend è realizzato come una Single Page Application facendo uso del framework Javascript Vue.
Per il backend si è scelto di adottare una strategia il più modulare possibile in modo tale che ciascun membro del team possa lavorare su una o più funzionalità senza intaccare il lavoro svolto dai suoi colleghi. Siccome il nostro progetto consiste in diverse funzionalità che sono però raggruppabili in tre macro categorie (gestione utenti, gestione stanze/prenotazioni, gestione club/channel), abbiamo basato la nostra architettura sull'idea del Modular Monolith. Come si può evincere dal nome, tale architettura cerca di combinare i vantaggi di una microservice architecture (ossia la modularità e indipendenza delle parti) con quelli di una monolith architecture (bassa complessità per progetti di ridotte dimensioni), minimizzando al minimo i svantaggi della prima (ossia, la sua elevata complessità) e della seconda (l'elevata coesione delle componenti può rendere lo sviluppo più complesso nel lungo periodo). Per un confronto più dettaglio delle tre architetture rimandiamo al seguente articolo.
Come in una microservice architecture, ciascun service implementa le funzionalità di una macro categoria. Vi sono pertanto tre service: user
(per la gestione degli utenti e del proprio account), room
(per la gestione delle aule e delle prenotazioni), club
(per la gestione dei club e dei relativi eventi e notizie). Tali moduli (ad eccezione di club
, ancora da implementare) possono essere trovati sotto backend/src/modules/
. Per assicurarci che tali moduli fossero il più possibile loosely coupled, abbiamo deciso di rendere la comunicazione tra questi asincrona usando un approccio event driven sfruttando un Event Bus. Anche lato database abbiamo deciso di garantire tale indipendenza utilizzando come una strategia schema-per-service (il database usato dai vari servizi è suddiviso tra di essi solo dal punto di vista logico) e data duplication (ogni servizio ha la propria copia di dati che gli servono per svolgere le proprie operazioni).
Dal momento che i vari moduli devono essere eseguiti nello stesso processo, abbiamo introdotto un modulo aggiuntivo, detto gateway
(che prende ispirazione dall'API Gateway nelle microservice architecture), il quale si occupa di inizializzare gli altri service (facendo uso dei metodi che essi mettono a disposizione) e di inizializzare un (Express) router per dirottare le richieste HTTP al modulo responsabile per la loro gestione.
All'interno di ciascun servizio, essi sono implementati con una hexagonal architecture (anche detta ports and adapters architecture). L'idea alla base di tale architettura è quella di separare le componenti in tre parti: application, domain e infrastructure. Il domain contiene tutta la business logic, ossia gli application service (non abbiamo distinto tra application e domain service come avviene nel Domain Driven Design) e le domain entity (anche dette model). Per garantire il principio della dependency inversion e far sì che la business logic sia indipendente dal "mondo esterno", essa definisce delle port (implementate come interfacce) per comunicare con quest'ultimo. In particolare, le input port (o driving port) espongono le funzionalità del domain (ma non la loro implementazione), consentendo quindi ai dati di fluire dal primo verso il secondo. Le output port (o driven port) sono invece usate dall'application core per accedere a funzionalità del mondo esterno (ad esempio, una repository).
Tali comunicazioni (mediate dalle porte) sono rese possibili dagli adapter. Gli inbound adapter (o application) sfruttano le input port per eseguire le funzionalità del domain sulla base degli input esterni. Nel nostro caso gli inbound adapter sono gli HTTP endpoints. Gli outbound adapters implementano le driven port in modo che la business logic possa usare tali servizi. In questo modo, il domain non deve preoccuparsi di come tali servizi siano implementati, ad esempio quale tipo di database si è scelto di usare, e tali modifiche sono limitate allàinfrastruttura. Nel nostro caso, gli inbound adapter sono il database e l'event broker.
Come database abbiamo scelto di usare un graph database (Neo4j) in quanto abbiamo ritenuto fosse una soluzione migliore per il nostro problema che prevede diverse relazioni tra le entità.
La repository contiene tutto il codice sia del frontend che del backend. Per quanto concerne il backend, ci siamo ispirati a tale repository per la sua organizzazione. In particolare, si tratta di una monorepo organizzata tramite rush dove ogni service è un package separato dagli altri così che le dipendenze di uno non intacchino quelle di un altro.
Di seguito mostriamo una struttura semplificata della repository.
root
| - frontend // codice per il frontend
| - backend // codice per il backend
| | - rush.json // file per la gestione della monorepo
| | - src // folder con il codice sorgente del backend
| | | - common // package contente modelli comuni a tutto il backend (errori, eventi, user-id)
| | | - gateway // modulo che si occupa di inizializzare il server, il router centrale e i vari moduli
| | | | - src
| | | | | - index.ts // inizializzazione del server
| | | | | - room.ts // inizializzazione del modulo room
| | | | | - user.ts // inizializzazione del modulo user
| | | - modules // folder contenente i diversi service
| | | | - user // service per la gestione degli utenti e l'autenticazione
| | | | | - package.json // ogni modulo ha le proprie dependency
| | | | | - tsconfig.json // typescript settings per il testing
| | | | | - tsconfig.build.json // typescript settings per il building
| | | | | - tests
| | | | | | - integration // integration tests
| | | | | | | - init_app // metodi per inizializzare il server (da usare con supertest)
| | | | | | | - init_db // metodi per inizializzare il database per il testing
| | | | | | | - ... // file contenenti le test suite
| | | | | - src
| | | | | | - application
| | | | | | | - rest // folder contenente gli HTTP endpoint
| | | | | | - domain
| | | | | | | - errors // errori restituiti da questo modulo
| | | | | | | - events // eventi emessi o ascoltati da questo modulo
| | | | | | | - models // domain enities
| | | | | | | - ports // definizione delle port come interfacce
| | | | | | | - services // implementazione delle input port
| | | | | | - infrastructure // implementazione delle driven port
| | | | | | | - repository
| | | | | | | - broker
| | | | - room // service per la gestione delle stanze e la loro prenotazione
| | | | - channel // service per la gestione dei club e dei channel (sprint 2)
| | | | - messenger // service per l'invio delle email e delle web notification
La branching strategy utilizzata rispecchia la struttura dei file usata per organizzare la repository (e quindi l'architettura stessa del progetto). In particolare, il branch principale è il main che contiene le versioni del frontend e del backend pronte per il deploy. Vi sono poi altri due long-lived branch, chiamati frontend e backend, i quali contengono rispettivamente il codice del frontend e del backend non ancora pronto per essere rilasciato.
Qualora sia necessario aggiungere nuove feature al frontend, viene creato un branch feature a partire dal frontend. Una volta terminata e verificata l'implementazione della feature, viene fatto il merge del branch feature sul branch frontend.
Per il backend, invece, ogni modulo ha un proprio long-lived branch (chiamato branch-module dove module corrisponde al nome di quel modulo) che contiene il codice relativo a quel modulo. Qualora si voglia aggiungere una nuova feature a tale modulo, si procede tramite branch feature come per il frontend. Fanno eccezione il modulo gateway e common, in quanto abbiamo ritenuto più comodo fare commit direttamente sul rispettivo branch date le loro ridotte funzionalità. Quando si ritiene che il codice di un modulo possa essere reso disponibile all'intero backend, viene fatto il merge del branch-module sul backend.
Nota: alcuni branch feature sono stati eliminati in quanto non avevamo visto la nota che indicava di non eliminarli.
Per la definizione di 'done' abbiamo preso come riferimento quelle fornite dai seguenti siti: Boost, Apiumhub, LeadingAgile.
- Documentazione aggiornata
- Codice adeguatamente commentato
- Test funzionali scritti e passati
- Test funzionali eseguiti da almeno un altro membro del team
- Codice revisionato da un altro membro del team
- Eseguito il deploy del progetto su un ambiente di test analogo a quello di produzione
- Codice rilasciato sul branch main
Nota. Nello sprint #1, i test non sono stati implementati ma soltano descritti in un Google Sheet. Per questo motivo, i punti 3 e 4 vengono sostituiti dai due punti seguenti:
- Test funzionali descritti
- Documentazione test revisionata da un membro del team diverso da chi l'ha scritta