Skip to content
Marco Di Francesco edited this page Dec 15, 2020 · 42 revisions

Software Engineering II Project

Laurea Triennale in Informatica, DISI, University of Trento

Questo progetto è una web application che stiamo sviluppando per il corso "Ingegneria del Software 2"

Project timeline

Date Milestone What to deliver
5 - 12 October Group Registration Team members name
15 October Main idea Short Statement of the idea
31 October Design Product backlog, Mockup
25 - 30 November Implementation (1st release) Sprint #1
15 December Implementation (2nd release) Sprint #2

Statement of our main project idea

We need to develop an online service for e-commerce that will allow University students to sell, buy and search for used books, notes and resources. We want to deliver a web version of this service and a client application.

Links

  • Link to Apiariy go
  • Link to Heroku go

How to demo

Requirements to demo offline:

  • Docker Engine ( >= 19.03 ) per creare immagini con all'interno il codice che poi verranno lanciate in container
  • Docker Compose ( versione >= 1.27 ) per orchestrare i container Docker specificando environment variables per il testing

How to build: una volta dentro la root directory del progetto bisogna fare il build delle immagini docker utilizzando il seguente comando: docker-compose build Successivamente lanciare i container con il seguente comando: docker-compose up Cercare nel browser l'indirizzo specificato dal log.

Variazioni rispetto alla milestone #1

Non ci sono stati cambiamenti per quanto riguarda la descrizione delle user story ma l’unico cambiamento che è stato effettuato è quello della stima delle ore di lavoro che dopo il primo scrum meeting si sono rivelate molto basse

Architettura

Per il frontend abbiamo utilizzato il framework React. Abbiamo preferito questo framework a Vue e Angular data la gestione degli stati e la conseguente prevedibilità del codice, oltre che agilità in sede di debugging. Sempre nel frontend abbiamo react-router per la gestione delle richieste http, bootstrap per la grafica (e.g. cards, navbar, jumbotron etc.), il tutto fatto girare con NodeJS versione 15. Per il backend abbiamo utilizzato express per la l'esposizione delle api utilizzata dalla nostra web application, node-fetch e axios per le richieste di tipo XMLHttpRequests e fetch, bcrypt per la cifratura delle password che verranno salvate nel backend, body-parser per creare un middleware in cui andiamo a filtrare le richieste che non hanno autenticazione, jsonwebtoken per la trasmissione di informazioni tra 2 parties utilizzando un json object in modo sicuro, mongoose per l'interfaccia con il database in MongoDB, morgan per la creazione di un middleware che permette di loggare le richieste e multer per la gestione di richieste ti tipo multipart/form-data. Come database per il backend abbiamo utilizzato MongoDB con il servizio gratuito Atlas.

Per la continuous integration abbiamo utilizzato la tecnologia di Trevis CI (sezione Testing per dettagli). Riguardate il continuous deployment abbiamo deprecare il servizio Github Actions con la possibilità di fare il deployment on commit in un branch separato (chiamato gh-pages) ed è per questo motivo che si vede un branch che parte dal nulla LINK. Questo avrebbe dato la possibilità di evitare il evitare lo sleep che Heroku obbliga ad avere quando un container (o applicazione non è utilizzata per più tempo). In ogni caso questo è stato rimpiazzato del deployment su Heroku sia del backend, e in questo caso siamo andati ad esporre sia fronted e il backend in 2 applicazioni separate utilizzando i container di Docker per svariate motivazioni di performance e security discusse sotto.

API versioning

Abbiamo voluto creare l'infrastruttura API con la possibilità di avere versione multiple delle API. Queste versioni sono accessibili modificando l'url della richiesta, ad esempio "example.com/v1/users" rispetto a "example.com/v2/users", questo ci permette di dare agli utenti che utilizzano le nostre API, la sicurezza che le response non cambieranno funzionamento nel futuro. Questa infrastruttura non è stata comunque utilizzata in questi 2 sprint, dato che abbiamo prestabilito in anticipo il funzionamento dei metodi, e di conseguenza non siamo andati a modificarli, e una versione v2 sarebbe stata solo superflua.

Organizzazione della repository

Il nostro progetto è strutturato in più directories, sicché ognuna di queste contenga una tecnologia differente; e ci siamo riferiti alle best practices per la strutturazione delle sottocartelle LINK. Abbiamo adottato una suddivisione 2 cartelle principali app e develop, in cui in app si trova tutto il frontend dell’applicazione, mentre in backend si trova la business logic. Le 2 cartelle contengono un Dockerfile, che viene utilizzato da Docker Compose per la creazione del container e la gestione dello stesso (affinché sia fatto partire e, conseguentemente, bloccato con rispettivamente i comandi docker-compose up e docker-compose down). Abbiamo deciso di tener separate le varie tecnologie per questioni di costi e performance e per il deployment in 2 servizi diversi (anche se alla fine abbiamo fatto il deployment in un servizio unico).

Strategia di branching

La nostra strategia di branching è descritta come "stability and feature based", ovvero abbiamo deciso di dare nomi ai branch a seconda delle feature e della stabilità del codice. Come regola generale abbiamo voluto dare dei nomi specifici che andassero a descrivere la stabilità in un certo branch, oppure la descrizione di una certa feature, non abbiamo creato dei branch che andavano a descrivere dei fix, unit testing o feature poco importanti dato che abbiamo voluto rendere i nomi dei branch self-explanatory, ovvero che sia leggibile da tutti la lista di branch senza andarsi a leggere il significato di quel branch nella documentazione.

Abbiamo innanzitutto separato il branch di production (master) e il branch in cui siamo andati a sviluppare le nuove feature (develop). Abbiamo fatto questa suddivisione nell'ottica di avere avere in master solamente una versione del nostro prodotto funzionante e stabile fruibile da tutti, ed avere una versione instabile in cui è possibile aggiungere feature che possono potenzialmente rendere il prodotto instabile. Nel branch develop abbiamo creato 2 branch contenenti codice per il backend e il frontend. Abbiamo adottato questa suddivisione nel primo sprint dato che abbiamo implementato separatamente il backend e il frontend, andando ad unirli solamente una volta implementate tutte le funzioni. Più in dettaglio:

  • api in cui siamo andati ad implementare le api nel backend in NodeJS
  • frontend in cui abbiamo implementato il frontend in React

Non abbiamo create un branch release (dividendolo da develop) dato che tutti gli sviluppatori si sono concentrati nella release, e non ci sarebbero stati commit contemporaneamente in release e in develop, rendendo appunto quel branch inutile.

Definizione di DONE

Quando tutti i punti della user story sono stati completati ed il prodotto è stato testato per quella sezione. I membri che hanno partecipato in quella user story devono revisionare il codice e controllare che sia stato implementato nel modo inteso da loro.

Il nostro progetto è strutturato in più directories, sicché ognuna di queste contenga una tecnologia differente; quanto alle best practices per la strutturazione, ci siamo rifatti a questi contenuti: LINK . Entrambe le cartelle (app e src) contengono un Dockerfile, che sarà utilizzato da Docker Compose per la creazione del container e la gestione dello stesso (affinché sia runnato e, conseguentemente, bloccato con docker-compose up e docker-compose down). Abbiamo deciso di tener separate le varie tecnologie per questioni di costi e performance. Abbiamo fatto il deploy del frontend (in React) su github actions viste le performance in confronto degli altri servizi, mentre abbiamo fatto il deploy della parte di backend (in NodeJS) su Heroku, come da consegna.

Committing strategy

Abbiamo voluto evitare di creare commit inutili come commit relativi a Work In Progress (WIP) che renderebbero la history della repository difficile da leggere, e per ogni commit che abbiamo creato abbiamo deciso che il la compilazione del progetto sarebbe dovuta andare a buon fine, in modo che sia possibile fare il checkout in qualsiasi commit e far partire la specifica versione. I messaggi sono stati limitati a 50 caratteri secondo le good practices di git-gsm LINK. Abbiamo talvolta fatto il rebase dei commit per unire il contenuto di commit multipli in uno per fare in modo che non ci siano 2 commit molto simili che andrebbero ad allugare inutilmente la history EXAMPLE

I messaggi dei commit di git sono stati pensati per essere leggibili e sono fatti in modo che immediatamente riconoscibile quando è stato fatto cosa. I messaggi hanno all'inizio un emoji, coerentemente a delle convenzioni semantiche da noi stabilite:

  • 📝 qualcosa di nuovo è stato aggiunto
  • 👀 è stata fatta una revisione/miglioria di un contenuto già presente
  • 🔨 sono stati modificati degli aspetti tecnici
  • 🌵 qualche pignolo ha voluto rifinire qualcosa

Security

Abbiamo pensato a molti aspetti di sicurezza del prodotto dato che in questi giorni. Innanzitutto tutti i servizi sono lanciati dentro a dei container isolati e questi container sono creati con una immagine ufficiale di Docker Hub: questo va ad evitare problemi di immagini contenenti codice malevolo LINK . Inoltre vengono utilizzate le immagini su Docker hub ufficiali anche per l'aggiornamento costante e per il rollout delle patch di sicurezza. Un’importante motivazione è sempre relativa sempre al remote code execution e, nello specifico, per evitare l'attacco di privilege escalation, che quindi permettere di uscire dall'utente che ha fatto partire l'entry point (nel nostro caso utente chiamato node) per poi accedere ai permessi di root e passare al controllo della macchina che ha fatto partire il container Docker LINK . Le immagini vengono create in Rootless Mode, ossia un utente chiamato node (al posto dell'utente root) si occuperà di lanciare l’applicazione (ad esempio "npm start") sicché sia più difficile risalire alla macchina che ha lanciato il Docker container in caso di attacchi come remote code execution. Dalla documentazione ufficiale LINK : Rootless mode allows running the Docker daemon and containers as a non-root user to mitigate potential vulnerabilities in the daemon and the container runtime.

Abbiamo pensato anche alla sicurezza dei dati relativi all'utente e siamo andati a cifrare le password utilizzando hashing e salting. Più in particolare nel metodo POST che supervisiona la creazione di un’istanza di utente e la sua conservazione nel db, la password inserita viene fatta passare per una funzione di hash, in modo che non venga vista in chiaro nel db; inoltre, per evitare falle di sicurezza dovute dall’eventuale presenza di passwords in rainbow tables, abbiamo fatto in modo di inserire un salt nella procedura di hashing. Per effettuare entrambe queste operazioni abbiamo utilizzato la libreria bcrypt.js LINK di Node.

Fintantoché ci troviamo in development environment, le credenziali di accesso al database MongoDB Atlas sono conservate in un file nodemon.json, in modo da poterle referenziare come variabili d’ambiente dichiarate esplicitamente in un file. Il database di development in questione non ha all'interno alcun dato sensibile e l'accesso da persone non autorizzate non comporterebbe problemi. Le credenziali di accesso al database in production sono invece passate come variabili d'ambiente e sono state specificate per quella macchina in particolare.

Mockup UI

Come strumento per realizzare il mockup abbiamo scelto Figma, in modo da facilitare la collaborazione da remoto. Il progetto è disponibile, sempre aggiornato, a questo link: figma Per le foto degli utenti abbiamo utilizzato il sito thispersondoesnotexist., che sfrutta un AI per creare volti di individui che non esistono realmente.