UNIBO
Department of Computer Science
Relazione progetto MaraffaOnline - SAP
Matteo Santoro
Supervisor: Alessandro ricci
2025-06-22
I, Matteo Santoro, del Dipartimento di Informatica dell’Università di
Bologna, confermo che il presente lavoro è di mia proprietà e che le
figure, le tabelle, le equazioni, i frammenti di codice, le opere d’arte
e le illustrazioni contenute in questa relazione sono originali e non
sono state tratte da lavori di altre persone, tranne nei casi in cui i
lavori di altri sono stati esplicitamente riconosciuti, citati e
referenziati. Sono consapevole che, in caso contrario, si configurerà un
caso di plagio. Il plagio è una forma di cattiva condotta accademica e
sarà sanzionato di conseguenza.
Firstname(s) Lastname
2025-06-22
This is an undergraduate project report template and instruction on how to write a report. It also has some useful examples to use LaTeX. Do read this template carefully. The number of chapters and their titles may vary depending on the type of project and personal preference. Section titles in this template are illustrative should be updated accordingly. For example, sections named “A section...” and “Example of ...” should be updated. The number of sections in each chapter may also vary. This template may or may not suit your project. Discuss the structure of your report with your supervisor.
Guidance on abstract writing: An abstract is a summary
of a report in a single paragraph up to a maximum of 250 words. An
abstract should be self-contained, and it should not refer to sections,
figures, tables, equations, or references. An abstract typically
consists of sentences describing the following four parts: (1)
introduction (background and purpose of the project), (2) methods, (3)
results and analysis, and (4) conclusions. The distribution of these
four parts of the abstract should reflect the relative proportion of
these parts in the report itself. An abstract starts with a few
sentences describing the project’s general field, comprehensive
background and context, the main purpose of the project; and the problem
statement. A few sentences describe the methods, experiments, and
implementation of the project. A few sentences describe the main results
achieved and their significance. The final part of the abstract
describes the conclusions and the implications of the results to the
relevant field.
Keywords: a maximum of five keywords/keyphrase
separated by commas
Report’s total word count: we expect a maximum of 20,000 words (excluding reference and appendices) and about 50 - 60 pages. [A good project report can also be written in approximately 10,000 words.]
School of Mathematical, Physical and Computational Sciences
Guidance on introduction chapter writing: Introductions are written in the following parts:
A brief description of the investigated problem.
A summary of the scope and context of the project, i.e., what is the background of the topic/problem/application/system/algorithm/experiment/research question/hypothesis/etc. under investigation/implementation/development [whichever is applicable to your project].
The aims and objectives of the project.
A description of the problem and the methodological approach adopted to solve the problem.
A summary of the most significant outcomes and their interpretations.
Organization of the report.
Consult your supervisor to check the content of the introduction chapter. In this template, we only offer basic sections of an introduction chapter. It may not be complete and comprehensive. Writing a report is a subjective matter, and a report’s style and structure depend on the “type of project” as well as an individual’s preference. This template suits the following project paradigms:
software engineering and software/web application development;
algorithm implementation, analysis and/or application;
science lab (experiment); and
pure theoretical development (not mention extensively).
Use only a single font for the body text. We recommend using a clean and electronic document friendly font like Arial or Calibri for MS-word (If you create a report in MS word). If you use this template, DO NOT ALTER the template’s default font “amsfont default computer modern”. The default LaTeX font “computer modern” is also acceptable.
The recommended body text font size is minimum 11pt and minimum one-half line spacing. The recommended figure/table caption font size is minimum 10pt. The footnote1 font size is minimum 8pt. DO NOT ALTER the font setting of this template.
Describe to a reader the context of your project. That is, what is your project and what its motivation. Briefly explain the major theories, applications, and/or products/systems/algorithms whichever is relevant to your project.
Cautions: Do not say you choose this project because of your interest, or your supervisor proposed/suggested this project, or you were assigned this project as your final year project. This all may be true, but it is not meant to be written here.
This section describes the investigated problem in detail. You can also have a separate chapter on “Problem articulation.” For some projects, you may have a section like “Research question(s)” or “Research Hypothesis” instead of a section on “Problem statement.’
Describe the “aims and objectives” of your project.
Aims: The aims tell a read what you want/hope to achieve at the end of the project. The aims define your intent/purpose in general terms.
Objectives: The objectives are a set of tasks you would perform in order to achieve the defined aims. The objective statements have to be specific and measurable through the results and outcome of the project.
Briefly describe the solution approach and the methodology applied in solving the set aims and objectives.
Depending on the project, you may like to alter the “heading” of this section. Check with you supervisor. Also, check what subsection or any other section that can be added in or removed from this template.
You may or may not need subsections here. Depending on your project’s needs, add two or more subsection(s). A section takes at least two subsections.
Depending on your project’s needs, add more section(s) and subsection(s).
The command \subsubsection{} creates a paragraph heading in LaTeX.
Write your text here...
Describe clearly what you have done/created/achieved and what the major results and their implications are.
Describe the outline of the rest of the report here. Let the reader know what to expect ahead in the report. Describe how you have organized your report.
Example: how to refer a chapter, section, subsection. This report is organised into seven chapters. Chapter [ch:lit_rev] details the literature review of this project. In Section [ch:method]...
Note: Take care of the word like “Chapter,” “Section,” “Figure” etc. before the LaTeXcommand \ref{}. Otherwise, a sentence will be confusing. For example, In [ch:lit_rev] literature review is described. In this sentence, the word “Chapter” is missing. Therefore, a reader would not know whether 2 is for a Chapter or a Section or a Figure.
Per ottenere maggior chiarezza sul dominio è stato utilizzato il metodo Event Storming. La tecnica consiste nell’individuare degli eventi di dominio e riportarli al passato su un post-it arancione. Una volta individuati gli eventi, sono stati scritti su post-it blu i comandi che l’utente svolge per creare l’evento. In giallo è stato specificato l’attore, la persona che esegue il comando, mentre in verde la view, l’interfaccia software con la quale l’utente interagisce. Infine, con delle label si sono aggregati i post-it in unità di dominio. Si riporta di seguito lo screen della lavagna con i post-it e la relativa legenda.
max width=1.1,center
Nome | Descrizione | Sinonimi |
---|---|---|
Mano | Distribuzione delle 40 carte ai 4 giocatori e la seguente serie di 10 prese | Round |
Mano | Carte dei giocatori non ancora giocate | Hand |
Presa | Quando ogni giocatore, a turno, gioca sul tavolo una carta. L’ultima presa della mano vale 1 punto. | Trick |
Partita | Insieme di più mani fino al raggiungimento del punteggio di 41 punti. | Game |
Partita corta | Insieme di più mani fino al raggiungimento del punteggio di 31 punti. | Short Game |
Tavolo | Raggruppamento di 4 giocatori, suddivisi in 2 coppie, i giocatori delle stessa squadra “siedono” in direzione opposta | Table |
Seme | Tipologia distintiva di carta, ne esistono 4: Denari, Coppe, Spade, Bastoni | Suit: Coins, Cups, Swords, Clubs |
Briscola | Seme con priorita’ piu’ alta. | Trump |
Maraffa | Se un giocatore possiede le tre carte di valore maggiore (asso, due e tre, dette assieme "Maraffa" o "Cricca") del seme di briscola, vince tre punti addizionali. In questo caso deve scendere con l’asso di quel seme. | Cricca, Marafon, Tresette con la Briscola |
Mazzo | 40 carte, di 4 semi diversi, 1,2,3,4,5,6,7, fante, cavallo e re. | Deck |
Taglio | Durante una mano in un seme viene giocato il seme di briscola, che avendo priorita’ maggiore permette di prendere nonostante il seme di gioco | Cut |
Busso | Invita il compagno, se possibile, a conquistare la presa e ad aprire il turno successivo con lo stesso seme | Knock |
Striscio corto | Quando si ha ancora in mano un basso numero di carte dello stesso seme con cui si è aperto il turno. | Short strip |
Striscio lungo | Quando si ha ancora in mano molte carte dello stesso seme con cui si è aperto il turno. | Long strip |
Volo | Quando non si hanno più carte del seme con cui si è aperto il turno. | Fly |
Figura | Fante, Cavallo, Re, con punteggio di 1/3 di punto. | Figure |
Asso | Carta con valore di 1 punto. | Ace |
Due e Tre | Carte con valore 1/3 di punto. | Two and Three |
Carta Liscia | Carte con numeri 4, 5, 6, 7. Sono prive di valore | Smooth paper |
Squadra | Coppie di giocatori seduti opposti | Team |
Giocatore | Persona che interagisce con l’applicativo | Player, User |
Chiamata fuori | Se un giocatore pensa che la sua squadra abbia raggiunto i 41 punti (o 31 punti nella variante "corta" della partita), la squadra può dichiarare di avere già nel mazzo delle prese i punti per vincere e chiudere in anticipo l’ultima partita. In questo modo la mano termina immediatamente, senza che vengano giocate le restanti prese e la squadra che si è "chiamata fuori" impedisce all’altra squadra di conquistare ulteriori prese. Se una squadra si chiama fuori e, dopo aver contato i punti delle prese effettuate ed averli sommati ai punti ottenuti nelle mani già giocate, non raggiunge i punti per la vittoria (in gergo "sbaglia la chiamata") scatta automatico l’11 a 0 per la squadra avversaria | Call out |
Modalità di gioco | regole di gioco classiche o varianti che influenzano aspetti come il punteggio, condizioni di vittoria/perdita, ... Ne sono state implementate due: classica, 11 a 0. | Game mode |
Analizzando il dominio di MaraffaOnline, sono stati identificati tre bounded context. Nella figura si può osservare un primo bounded context, colorato di azzurro, che modella l’autenticazione dell’utente:
User: persona che possiede l’account
Statistic: dati dell’utente relativi al gioco come numero di vittorie, sconfitte, partite giocate e maraffe
Authentication: accesso all’applicativo MaraffaOnline da parte dell’utente
Il secondo bounded context, colorato di verde, modella la partita:
Game: partita
Team: squadra composta da numero di giocatori / 2
Score: punteggio delle due squadre
Statistic: dati relativi ai game giocati
Trick: presa di quattro carte da parte di un giocatore
Round: 10 prese
Card: carta
Player: giocatore
Deck: mazzo
Hand: carte che ha in mano un giocatore
Infine l’ultimo, di colore arancione, modella la chat:
Chat: chat di gioco
Message: messaggio inviato nella chat
User: persona che invia il messaggio
È importante notare che il concetto di user/player è polisemico: vi sono tre rappresentazioni diverse per afferire allo stesso concetto.
Account
Login
Registrazione
Recupero password
Visualizzazione profilo
Modifica password
Possibilità di scegliere se giocare come ospite o effettuare il login
Realizzazione partita
Creazione partita
Partecipazione partita
gioca carta
Inizio partita
Fine mano
Fine partita
Chat di gioco
chat globale
chat partita
Possibilità di scegliere un compagno di squadra
Scelta del seme, parole consentite
Modalità di gioco 11 a 0
Gestione punteggio
Calcolo totale e parziale (Gestione per ogni mano) del punteggio
Maraffa/Cricca (+3 punti)
Servizio gestione utenti
Salvataggio statistiche
Realizzazione GUI
Refactor della GUI esistente
Rinnovamento GUI
Si riporta di seguito lo schema dei casi d’uso che modella l’interazione dell’utente con l’applicazione.
Per poter mantenere le varie parti di software evolvibili e coerenti ai contesti del dominio. L’architettura utilizza un servizio, con il ruolo di customer all’interno del pattern Customer-supplier, che "coordina" ed interroga gli altri. Di seguito, si riportano le motivazione secondo le quali è stato scelto:
servizi loosely coupled
evoluzione indipendente dei servizi, durante l’aggiornamento del sistema.
ove possibile, ogni bounded context è stato sviluppato in un microservizio separato, ottenendo, così, una distinta separazione della business logic e delle responsabilità.
La manutenibilità dei singoli componenti che possono, ipoteticamente, essere sviluppati da team diversi in modo parallelo, definendo al meglio i confini del dominio.
Una pratica del DDD è l’Event Sourcing, tuttavia, in questo progetto non è stato applicato. Infatti, sebbene possa trarre in inganno il salvataggio su MongoDB di tutti gli eventi che si verificano nel contesto di una partita e, di conseguenza, la memorizzazione degli stati, l’Event Sourcing non è rispettato. Gli stati salvati sono mutabili e vengono sovrascritti ad ogni cambiamento grazie ai metodi di aggiornamento, senza salvare ogni singola modifica. Inoltre, sebbene sia presente un timestamp al momento della creazione del record, non sono presenti in fase di aggiornamento. Per evitare di aggiungere ulteriore complessità al progetto, anche in termini di performance relativa alle interrogazioni articolate per ricostruire lo storico, si è deciso di non implementare il pattern. Oltre a una semplificata manutenzione, si va anche a ridurre lo spazio di memorizzazione necessario e, pertanto, in questa casistica si è ritenuto che un approccio tradizionale fosse più che sufficiente.
L’architettura del software è basata su micro-servizi autonomi e indipendenti tra loro, la cui comunicazione avviene tramite API REST. Esiste un middleware che si occupa della gestione e dell’orchestrazione dei micro-servizi, fungendo da ponte per collegare le varie tecnologie.
I microservizi sviluppati si occupano di varie aree legate alla natura del progetto e sono:
UserService: si occupa della gestione degli utenti, quindi la loro registrazione, autenticazione e gestione dei dati personali.
BusinessLogic: si occupa di mantenere al proprio interno tutte le regole del gioco.
Front-End: si occupa di gestire l’interfaccia grafica e la comunicazione con il middleware.
Middleware: si occupa della gestione delle comunicazioni tra i vari microservizi e svolge la funzione di "motore di gioco".
TODO ricorda di scrivere per bene integration tests, vedi slide 2.6
Per ogni servizio in questo sistema è stato adottato, ove possibile, il paradigma di programmazione TDD, cioè Test Driven Development. Questo approccio prevede che i test siano scritti prima del codice, in modo da guidare lo sviluppo e garantire che il codice prodotto soddisfi i requisiti specificati. Le operazioni di testing sono stata implementate con Jest per quanto riguarda i servizi che usano Node.js, generando di fatto un report abbastanza comprensibile. Il componente middleware, scritto in Java, invece, generava risultati di test non così chiari. Per modificare questo comportamento, è stata introdotta una dipendenza nel build Gradle che permette di avere un report più dettagliato e comprensibile. La libreria Test Logger, tramite una piccola configurazione nel file build.gradle come qui riportata, ha soddisfatto le nostre esigenze.
import com.adarshr.gradle.testlogger.TestLoggerExtension
import com.adarshr.gradle.testlogger.TestLoggerPlugin
import com.adarshr.gradle.testlogger.theme.ThemeType
{
testlogger = ThemeType.MOCHA
theme = true
showExceptions = true
showStackTraces = false
showFullStackTraces = true
showCauses = 2000
slowThreshold = true
showSummary = false
showSimpleNames = true
showPassed = true
showSkipped = true
showFailed = false
showOnlySlow = false
showStandardStreams = true
showPassedStandardStreams = true
showSkippedStandardStreams = true
showFailedStandardStreams = LogLevel.LIFECYCLE
logLevel }
L’architettura dei componenti si può riassumere in questo modo:
Classi controller: che hanno la funzione di esporre le rotte e di contenere al loro interno classi di servizio.
Classi di servizio: che contengono la logica di business.
I test vengono quindi eseguiti sulle classi di servizio, andando a testare la logica di business e non le funzioni. Questo approccio permette di avere una copertura maggiore del codice e di garantire che la logica di business sia corretta. Un esempio di test su una classe di servizio, prendendo come esempio il componente userManagement, rende chiaro il concetto.
Questo componente di fatto svolge due funzioni ben distinte:
Collegarsi a un database MySQL e fare delle operazioni CRUD su tabelle di utenti e le loro rispettive statistiche.
Gestire la registrazione e l’autenticazione degli utenti.
La parte di testing quindi non va assolutamente a verificare che la connessione al database funzioni, ma che le operazioni CRUD siano corrette e che la registrazione e l’autenticazione degli utenti avvengano correttamente, così come l’aggiornamento delle statistiche.
Avendo scelto un’architettura a microservizi, è stato necessario testare anche il corretto funzionamento delle comunicazioni tra i servizi. Per fare ciò, è stato necessario creare dei test di integrazione che verificassero il corretto funzionamento delle API esposte dai servizi. Questi test sono stati implementati nel componente middleware che, come sicuramente si ricorderà, ha il compito di mettere in comunicazione i servizi tra di loro e con il frontend.
Durante la scrittura dei test all’interno del servizio in Java, alcune operazioni erano asincrone e quindi è stato necessario attendere che queste operazioni terminassero prima di poter eseguire i test e validarli. Per fare ciò, la normale struttura di test fornita da Vertx in un primo tentativo non era sufficiente. La classica struttura dei test è la seguente:
@Test
public void createGameTest(final VertxTestContext context) {
final JsonObject gameResponse = this.gameService.createGame(MARAFFA_PLAYERS, TEST_USER, EXPECTED_SCORE, GAME_MODE.toString(), PASSWORD);
.assertEquals(UUID_SIZE, gameResponse.getString(Constants.GAME_ID).length()); // Assuming UUID is 36
Assertions.completeNow();
context}
Per risolvere questo problema sono stati utilizzati:
il decoratore fornito da JUnit Jupiter @Timeout, che permette di aspettare un tempo definito prima di fallire automaticamente il test, per ovviare al problema di asincronicità con servizi esterni al componente stesso che non "rispondono alla chiamata".
l’utilizzo asincrono del VertxTestContext, che permette di completare il test solo quando tutte le operazioni asincrone sono state completate.
una struttura di risposta nei servizi con cui il middleware comunica, che permette di individuare errori tramite la chiave "error" in caso di fallimento contenuta all’interno del JSON di risposta. Il test non può accedere alla response di una chiamata HTTP che normalmente avrebbe uno status code 200 in caso di successo e quindi si è ricorso a questo espediente per individuare errori nei dati inviati al servizio.
Come è possibile vedere nel test riportato di seguito, seguendo questi accorgimenti è possibile testare correttamente le operazioni asincrone.
@Timeout(value = 10, unit = TimeUnit.SECONDS)
@Test
public void testgetShuffledDeckOK(final VertxTestContext context) {
final JsonObject gameResponse = this.gameService.createGame(4, TEST_USER, 41,
.CLASSIC.toString(), PASSWORD);
GameModethis.businessLogicController
.getShuffledDeck(UUID.fromString(gameResponse.getString(Constants.GAME_ID)), 4)
.whenComplete((res, err) -> {
.verify(() -> {
contextassertNull(res.getString("error"));
.completeNow();
context});
});
}
Vedi Deployment slide 2.7
Per migliorare la qualità del software e velocizzare il processo di sviluppo, è stata implementata la Continuous Integration. Questa pratica permette di integrare il codice frequentemente, evitando possibili rischi d’integrazione e consetendo l’esecuzione di test automatici. Grazie al sistema di notifiche, inoltre, è possibile ricevere feedback immediato sullo stato delle Github Actions, per accertarsi che sia sempre tutto funzionante e non siano stati introdotti bug. Mantenendo il sistema continuamente monitorato, si migliora anche l’effiicienza e la collaborazione tra i membri del team.
Per ogni servizio in questo sistema è stato adottato, ove possibile, il paradigma di programmazione TDD, cioè Test Driven Development. Questo approccio prevede che i test siano scritti prima del codice, in modo da guidare lo sviluppo e garantire che il codice prodotto soddisfi i requisiti specificati. All’interno della CI, la fase di test è stata implementata con Jest per quanto riguarda i servizi che usano Node.js, generando di fatto un report abbastanza comprensibile direttamente nella finestra della CI. Il componente middleware, scritto in Java, invece, generava risultati di test non così chiari. Per modificare questo comportamento, è stata introdotta una dipendenza nel build Gradle che permette di avere un report più dettagliato e comprensibile. La libreria Test Logger, tramite una piccola configurazione nel file build.gradle come qui riportata, ha soddisfatto le nostre esigenze.
import com.adarshr.gradle.testlogger.TestLoggerExtension
import com.adarshr.gradle.testlogger.TestLoggerPlugin
import com.adarshr.gradle.testlogger.theme.ThemeType
{
testlogger = ThemeType.MOCHA
theme = true
showExceptions = true
showStackTraces = false
showFullStackTraces = true
showCauses = 2000
slowThreshold = true
showSummary = false
showSimpleNames = true
showPassed = true
showSkipped = true
showFailed = false
showOnlySlow = false
showStandardStreams = true
showPassedStandardStreams = true
showSkippedStandardStreams = true
showFailedStandardStreams = LogLevel.LIFECYCLE
logLevel }
È stato trovato un utilissimo strumento per poter velocizzare il processo di creazione della CI, Act. Questo strumento permette di eseguire le GitHub Actions localmente senza dover obbligatoriamente fare un push sul repository e quindi senza "sporcare" la cronologia dei commit. Richiede che Docker sia attivo ed è in grado di creare un container che utilizza l’immagine specificata nel file di configurazione della CI e di eseguire i job. Per eseguire Act è stato creato uno script in bash per poter automatizzare ulteriormente il testing delle CI, in cui viene letto il contenuto del file .env.example che popolerà l’environment del container con le variabili d’ambiente necessarie per il corretto funzionamento della CI, e le imposta come variabili d’ambiente della GitHub Action che si sta testando. Per poter usare Act è necessario semplificare leggermente il trigger per i job, che in ambiente di produzione è solitamente la chiusura di una pull request, mentre per testare velocemente è necessario impostarlo su una push.
Data l’architettura del progetto in microservizi, usare Docker per creare un’immagine per ogni servizio è stata una scelta naturale. L’obiettivo finale è avere un sistema facilmente deployabile e scalabile, eseguibile velocemente in un ambiente generico, con uno stack di container Docker che racchiuda tutto il sistema. Per fare ciò è stato necessario creare un Dockerfile per ogni servizio, in modo da poter creare un’immagine durante la fase di deploy della continuous integration.
La strategia di containerizzazione per tutte le immagini create è stata quella di utilizzare un’immagine base di Alpine, in modo da avere immagini leggere e veloci da scaricare. Per ridurre la dimensione delle immagini, si è utilizzata un’immagine di sviluppo per compilare il codice e un’immagine di produzione per eseguire il codice compilato, copiando solo i file necessari.
I servizi UserManagementMaraffa e BusinessLogic sono stati containerizzati utilizzando un’immagine di Node.js. Nell’esempio sotto, si può notare la distinzione tra i diversi stage.
20-alpine as base
FROM node:/app
WORKDIR /app/
COPY yarn.lock package.json
RUN yarn install/app
COPY .
RUN yarn build
20-alpine
FROM node:/app
WORKDIR --from=base /app/package.json /app/package.json
COPY --from=base /app/node_modules /app/node_modules
COPY --from=base /app/dist /app/dist
COPY 3000
EXPOSE "node","dist/main.js"] CMD [
La containerizzazione del middleware ha richiesto alcuni passaggi aggiuntivi rispetto ai servizi in Node.js. È stato necessario adottare immagini diverse per gli stage di build e produzione, quindi usare un’immagine di Gradle per la build, nella quale eseguire i comandi di Gradle, e un’immagine di OpenJDK per l’esecuzione del JAR prodotto dalla build.
Per la compilazione con Gradle, nonostante non sia una pratica corretta, è stato necessario mantenere nella repository il file gradle-wrapper.jar, in quanto non era possibile scaricarlo durante la build all’interno delle GitHub Actions. La build non utilizza il classico comando di Gradle per generare il file .jar, ma un task personalizzato che si occupa di generare il fatJar del servizio, poiché le dipendenze non venivano gestite correttamente all’interno del normale file .jar.
.register<Jar>("fatJar") {
tasks.set("Middleware")
archiveBaseName{
manifest ["Main-Class"] = "server.Main"
attributes}
from(sourceSets.main.get().output)
dependsOn(configurations.runtimeClasspath)
from({ configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) } })
dependsOn("compileJava")
= DuplicatesStrategy.EXCLUDE // Puoi utilizzare altre strategie come DuplicatesStrategy.WARN per avvisare ma non fermare la build
duplicatesStrategy }
La containerizzazione del frontend ha richiesto l’utilizzo di un’immagine di Node.js per lo stage di build, mentre per l’esecuzione è stata utilizzata un’immagine di Nginx. Nginx serve per eseguire l’applicazione all’interno del container e per poterla raggiungere dall’esterno. Per fare ciò è stato necessario configurare Nginx tramite un file di configurazione che ha permesso anche di gestire un’operazione di reverse proxy per indirizzare le chiamate al backend.
server {80;
listen
/ {
location /usr/share/nginx/html;
root ;
index index.html index.htm/ /index.html;
try_files $uri $uri
}
/api/ {
location "http://${API_HOST}:${API_PORT}/";
proxy_pass 1.1;
proxy_http_version ;
proxy_set_header Upgrade $http_upgrade'upgrade';
proxy_set_header Connection ;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade
} }
L’indirizzo del backend è stato configurato tramite variabili d’ambiente, in modo da poterlo cambiare facilmente in base all’ambiente in cui si trova il container, e queste variabili vengono sostituite a seconda dell’ambiente di deploy in cui l’applicativo Angular viene eseguito.
Data la scelta di dockerizzare interamente i servizi, è stato necessario l’utilizzo di variabili d’ambiente. Questo ha permesso di creare un sistema di deploy molto flessibile, in cui è possibile cambiare l’indirizzo del servizio a cui connettersi semplicemente modificando il file di configurazione del Docker Compose. I servizi in NodeJS leggono il contenuto delle variabili d’ambiente tramite il modulo ‘process.env‘, mentre i servizi in Java utilizzano ‘System.getenv‘. Questi comportamenti sono corretti per lo sviluppo locale e per ambienti di produzione in cui le variabili d’ambiente sono settate correttamente.
Per quanto riguarda invece lo sviluppo in CI, è stato necessario creare un file ‘.env.example‘ che contenesse tutte le variabili d’ambiente necessarie al funzionamento del servizio, in modo da poterle settare correttamente nel CI/CD, soprattutto per la fase di testing, e metterlo sulla repository. È importante tenere a mente che non dovrebbero mai essere caricati dati sensibili su strumenti di controllo di versione. In questo caso, non ci sono database o account cloud eventualmente raggiungibili dall’esterno.
In un progetto composto da microservizi deployati con container, il monitoraggio è essenziale per garantire il corretto funzionamento e la performance ottimale del sistema. Per realizzare un monitoraggio efficace dei container, sono stati impiegati i container di Grafana, Prometheus e cAdvisor. Si ringrazia il lavoro di Soham Mohite per la configurazione e l’integrazione di questi strumenti.
Grafana è uno strumento open-source per la visualizzazione e l’analisi delle metriche raccolte. Viene utilizzato per creare dashboard personalizzate che mostrano lo stato e le performance dei microservizi. Con Grafana, è possibile configurare alert che notificano immediatamente eventuali problemi nel sistema, permettendo una risposta rapida e mirata.
Prometheus è un sistema di monitoraggio e di allarme progettato per raccogliere e memorizzare metriche in serie temporali. È stato configurato per raccogliere metriche dai container dei microservizi e da cAdvisor. Prometheus esegue la raccolta dei dati a intervalli regolari e li memorizza in un database time-series, rendendoli disponibili per l’analisi e la visualizzazione in Grafana.
cAdvisor (Container Advisor) è uno strumento che fornisce informazioni sulle risorse utilizzate dai container, come CPU, memoria, rete e disco. È stato integrato con Prometheus per raccogliere e esportare le metriche dei container. cAdvisor offre una visione dettagliata delle performance di ogni container, aiutando a identificare e risolvere problemi di utilizzo delle risorse.
L’integrazione di Grafana, Prometheus e cAdvisor ha permesso di creare un sistema di monitoraggio completo. I container di cAdvisor raccolgono le metriche di utilizzo delle risorse dai container dei microservizi e le esportano a Prometheus. Prometheus memorizza queste metriche e le rende disponibili per la visualizzazione in Grafana. Le dashboard di Grafana sono configurate per mostrare le metriche chiave e fornire un’analisi dettagliata dello stato del sistema.
La configurazione di questi strumenti è stata gestita tramite Docker Compose, permettendo una facile implementazione e scalabilità del sistema di monitoraggio. Le variabili d’ambiente e i file di configurazione sono stati impostati per garantire la corretta connessione e funzionamento dei container di monitoraggio.
cadvisor:
container_name: cadvisor/cadvisor/cadvisor:latest
image: gcr.io
ports:- "8080:8080"
volumes:- "/:/rootfs"
- "/var/run:/var/run"
- "/sys:/sys"
- "/var/lib/docker/:/var/lib/docker"
- "/dev/disk/:/dev/disk"
privileged: true
devices:- "/dev/kmsg"
prometheus:
container_name: prometheus/prometheus:latest
image: prom
ports:- "9090:9090"
volumes:- "./prometheus.yml:/etc/prometheus/prometheus.yml"
privileged: true
depends_on:- cadvisor
grafana:
container_name: grafana/grafana:latest
image: grafana
ports:- "3000:3000"
environment:- GF_PATHS_PROVISIONING=/etc/grafana/provisioning
- DS_PROMETHEUS=prometheus
volumes:- "grafana-data:/var/lib/grafana"
- "./datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml"
- "./dashboard.json:/var/lib/grafana/dashboards/dashboard.json"
- "./default.yaml:/etc/grafana/provisioning/dashboards/default.yaml"
privileged: true
depends_on:- prometheus
Con questa configurazione, il sistema di monitoraggio offre una visione completa delle performance e dello stato dei microservizi, permettendo di mantenere un’operatività ottimale e di intervenire prontamente in caso di anomalie.
Infine il risultato prodotto è stato un file docker-compose che raggruppa tutti i servizi in un unico stack, definendo le dipendenze tra i container e semplificando il deploy è stato creato un network interno per poter permettere la comunicazione dei container tra di loro e particolari network, invece, per connettere i database ai servizi che ne fanno uso. Sono state inserite anche delle dipendenze tra i servizi, ad esempio lo user service ha come vincolo che il container del database sia avviato prima di esso. Sono stati inseriti dei servizi che permettono di monitorare i database:
Adminer: un’interfaccia web per la gestione dei database, che permette di visualizzare i dati, creare tabelle, eseguire query e molto altro.
Mongo Express: un’interfaccia web per la gestione dei database MongoDB.
"3.8"
version:
networks:
monitoring:
internal:
db:
services:
mysql:5.7
image: mysql:
networks:- internal
- db
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: ${MARAFFA_SQL_DB}
MYSQL_USER: ${MARAFFA_SQL_USER}
MYSQL_PASSWORD: ${MARAFFA_SQL_PWD}
ports:- "3306:3306"
volumes:- mysql_volume:/var/lib/mysql
mongo:5.0
image: mongo:
networks:- internal
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PWD}# MONGO_INITDB_DATABASE: your_mongo_database
expose:- 27012
ports:- 27012:27017
volumes:- mongo_volume:/data/db
adminer:
image: adminer:latest
networks:- db
ports:- "8090:8080"
depends_on:- mysql
-express:
mongo-express:1.0.2
image: mongo
networks:- internal
ports:- "8081:8081"
environment:
ME_CONFIG_MONGODB_SERVER: mongo//${MONGO_USER}:${MONGO_PWD}@mongo:27017/
ME_CONFIG_MONGODB_URL: mongodb:
ME_CONFIG_MONGODB_AUTH_USERNAME: ${MONGO_USER}
ME_CONFIG_MONGODB_AUTH_PASSWORD: ${MONGO_PWD}
depends_on:- mongo
-service:
user/sofy24/user-management-maraffa:${USER_SERVICE_TAG}
image: ghcr.io# container_name: user_service_container
networks:- db
- internal
ports:- "3001:3001"
environment:
DB_HOST: mysql3306
DB_PORT:
DB_USERNAME: ${MARAFFA_SQL_USER}
DB_PASSWORD: ${MARAFFA_SQL_PWD}
DB_NAME: ${MARAFFA_SQL_DB}3001
PORT:
depends_on:- mysql
-logic:
business/sofy24/business-logic-maraffa:${BL_TAG}
image: ghcr.io
networks:- internal
ports:- "3000:3000"
depends_on:- mysql
-app:
angular/mega2799/maraffa-fe:${FE_TAG}
image: ghcr.io
networks:- internal
ports:- "80:80"
depends_on:- middleware
environment:# API_HOST: ${API_HOST}
# API_PORT: ${API_PORT}
API_HOST: middleware# API_HOST: maraffaonlinepika-api-1
3003
API_PORT:
middleware:/sofy24/middleware-maraffa:${MIDDLEWARE_TAG}
image: ghcr.io
ports:- 3003:3003
networks:- internal
depends_on:- mongo
environment:# MIDDLEWARE_HOST: 127.0.0.1
3003
MIDDLEWARE_PORT: -logic
BUSINESS_LOGIC_HOST: business3000
BUSINESS_LOGIC_PORT: -service
USER_HOST: user3001
USER_PORT:
MONGO_USER: ${MONGO_USER}
MONGO_PASSWORD: ${MONGO_PWD}
MONGO_HOST: mongo27017
MONGO_PORT:
MONGO_DATABASE: ${MONGO_DB}
DEBUG: true
cadvisor:
container_name: cadvisor/cadvisor/cadvisor:latest
image: gcr.io
ports:- "8080:8080"
volumes:- "/:/rootfs"
- "/var/run:/var/run"
- "/sys:/sys"
- "/var/lib/docker/:/var/lib/docker"
- "/dev/disk/:/dev/disk"
privileged: true
devices:- "/dev/kmsg"
prometheus:
container_name: prometheus/prometheus:latest
image: prom
ports:- "9090:9090"
volumes:- "./prometheus.yml:/etc/prometheus/prometheus.yml"
privileged: true
depends_on:- cadvisor
grafana:
container_name: grafana/grafana:latest
image: grafana
ports:- "3005:3000"
environment:- GF_PATHS_PROVISIONING=/etc/grafana/provisioning
- DS_PROMETHEUS=prometheus
volumes:- "grafana-data:/var/lib/grafana"
- "./datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml"
- "./dashboard.json:/var/lib/grafana/dashboards/dashboard.json"
- "./default.yaml:/etc/grafana/provisioning/dashboards/default.yaml"
privileged: true
depends_on:- prometheus
volumes:
mysql_volume:
mongo_volume:
rabbitmq_data:-data: grafana
Some lengthy tables, codes, raw data, length proofs, etc. which are very important but not essential part of the project report goes into an Appendix. An appendix is something a reader would consult if he/she needs extra information and a more comprehensive understating of the report. Also, note that you should use one appendix for one idea.
An appendix is optional. If you feel you do not need to include an appendix in your report, avoid including it. Sometime including irrelevant and unnecessary materials in the Appendices may unreasonably increase the total number of pages in your report and distract the reader.
...
Example footnote: footnotes are useful for adding external sources such as links as well as extra information on a topic or word or sentence. Use command \footnote{...} next to a word to generate a footnote in LaTeX.↩︎