UNIBO
Department of Computer Science

Relazione progetto MaraffaOnline - SAP

 
Matteo Santoro
Supervisor: Alessandro ricci

2025-06-22

Declaration

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

Abstract

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.]

List of Abbreviations

School of Mathematical, Physical and Computational Sciences

Introduction

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:

  1. software engineering and software/web application development;

  2. algorithm implementation, analysis and/or application;

  3. science lab (experiment); and

  4. 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.

Background

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.

Problem statement

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.’

Aims and objectives

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.

Solution approach

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.

A subsection 1

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.

A subsection 2

Depending on your project’s needs, add more section(s) and subsection(s).

A subsection 1 of a subsection

The command \subsubsection{} creates a paragraph heading in LaTeX.

A subsection 2 of a subsection

Write your text here...

Summary of contributions and achievements

Describe clearly what you have done/created/achieved and what the major results and their implications are.

Organization of the report

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.

Domain Driven Design

Knowledge crunching session for the exploration of the problem space

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.

Event Storming
Event Storming Legend

Ubiquitos Language

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

Bounded Context

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.

Context Map

Requisiti e casi d’uso

Requisiti

  1. Account

    1. Login

    2. Registrazione

    3. Recupero password

    4. Visualizzazione profilo

    5. Modifica password

    6. Possibilità di scegliere se giocare come ospite o effettuare il login

  2. Realizzazione partita

    1. Creazione partita

    2. Partecipazione partita

    3. gioca carta

    4. Inizio partita

    5. Fine mano

    6. Fine partita

  3. Chat di gioco

    1. chat globale

    2. chat partita

  4. Possibilità di scegliere un compagno di squadra

  5. Scelta del seme, parole consentite

  6. Modalità di gioco 11 a 0

  7. Gestione punteggio

    1. Calcolo totale e parziale (Gestione per ogni mano) del punteggio

    2. Maraffa/Cricca (+3 punti)

  8. Servizio gestione utenti

  9. Salvataggio statistiche

  10. Realizzazione GUI

    1. Refactor della GUI esistente

    2. Rinnovamento GUI

Casi d’uso

Si riporta di seguito lo schema dei casi d’uso che modella l’interazione dell’utente con l’applicazione.

Schema dei casi d’uso

Pattern del DDD

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.

Design generale del software

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".

image

Testing

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 {
    theme = ThemeType.MOCHA
    showExceptions = true
    showStackTraces = true
    showFullStackTraces = false
    showCauses = true
    slowThreshold = 2000
    showSummary = true
    showSimpleNames = false
    showPassed = true
    showSkipped = true
    showFailed = true
    showOnlySlow = false
    showStandardStreams = false
    showPassedStandardStreams = true
    showSkippedStandardStreams = true
    showFailedStandardStreams = true
    logLevel = LogLevel.LIFECYCLE
}

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.

Testing asincrono

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);
    Assertions.assertEquals(UUID_SIZE, gameResponse.getString(Constants.GAME_ID).length()); // Assuming UUID is 36
    context.completeNow();
}

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,
            GameMode.CLASSIC.toString(), PASSWORD);
    this.businessLogicController
            .getShuffledDeck(UUID.fromString(gameResponse.getString(Constants.GAME_ID)), 4)
            .whenComplete((res, err) -> {
                context.verify(() -> {
                    assertNull(res.getString("error"));
                    context.completeNow();
                });
            });
}

Deployment

Introduzione

Vedi Deployment slide 2.7

Continuous Integration

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.

Automated quality assurance

Testing

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 {
    theme = ThemeType.MOCHA
    showExceptions = true
    showStackTraces = true
    showFullStackTraces = false
    showCauses = true
    slowThreshold = 2000
    showSummary = true
    showSimpleNames = false
    showPassed = true
    showSkipped = true
    showFailed = true
    showOnlySlow = false
    showStandardStreams = false
    showPassedStandardStreams = true
    showSkippedStandardStreams = true
    showFailedStandardStreams = true
    logLevel = LogLevel.LIFECYCLE
}

Act per testare CI

È 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.

Containerization

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.

image

Comparazione tra le immagini multi-stage e le immagini tradizionali

NodeJS

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.

FROM node:20-alpine as base
WORKDIR /app
COPY yarn.lock package.json /app/
RUN yarn install
COPY . /app
RUN yarn build

FROM node:20-alpine
WORKDIR /app    
COPY --from=base /app/package.json /app/package.json
COPY --from=base /app/node_modules /app/node_modules
COPY --from=base /app/dist /app/dist
EXPOSE 3000
CMD ["node","dist/main.js"]

Java

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.

tasks.register<Jar>("fatJar") {
    archiveBaseName.set("Middleware")
    manifest {
        attributes["Main-Class"] = "server.Main"
    }
    from(sourceSets.main.get().output)
    dependsOn(configurations.runtimeClasspath)
    from({ configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) } })
    dependsOn("compileJava")
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE // Puoi utilizzare altre strategie come DuplicatesStrategy.WARN per avvisare ma non fermare la build
}

Angular

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 {
    listen 80;
    
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass "http://${API_HOST}:${API_PORT}/";
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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.

Lettura variabili d’ambiente

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.

Monitoraggio

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.

image

Grafana

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

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

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.

Integrazione e Configurazione

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
    image: gcr.io/cadvisor/cadvisor:latest
    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
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - "./prometheus.yml:/etc/prometheus/prometheus.yml"
    privileged: true
    depends_on:
      - cadvisor

  grafana:
    container_name: grafana
    image: grafana/grafana:latest
    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.

Docker-compose

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.

version: "3.8"

networks:
  monitoring:
  internal:
  db:

services:
  mysql:
    image: mysql:5.7
    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:
    image: mongo:5.0
    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

  mongo-express:
    image: mongo-express:1.0.2
    networks:
      - internal
    ports:
      - "8081:8081"
    environment:
      ME_CONFIG_MONGODB_SERVER: mongo
      ME_CONFIG_MONGODB_URL: mongodb://${MONGO_USER}:${MONGO_PWD}@mongo:27017/
      ME_CONFIG_MONGODB_AUTH_USERNAME: ${MONGO_USER}
      ME_CONFIG_MONGODB_AUTH_PASSWORD: ${MONGO_PWD}
    depends_on:
      - mongo

  user-service:
    image: ghcr.io/sofy24/user-management-maraffa:${USER_SERVICE_TAG}
    # container_name: user_service_container
    networks:
      - db
      - internal
    ports:
      - "3001:3001"
    environment:
      DB_HOST: mysql
      DB_PORT: 3306
      DB_USERNAME: ${MARAFFA_SQL_USER}
      DB_PASSWORD: ${MARAFFA_SQL_PWD}
      DB_NAME: ${MARAFFA_SQL_DB}
      PORT: 3001
    depends_on:
      - mysql

  business-logic:
    image: ghcr.io/sofy24/business-logic-maraffa:${BL_TAG}
    networks:
      - internal
    ports:
      - "3000:3000"
    depends_on:
      - mysql

  angular-app:
    image: ghcr.io/mega2799/maraffa-fe:${FE_TAG}
    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
      API_PORT: 3003

  middleware:
    image: ghcr.io/sofy24/middleware-maraffa:${MIDDLEWARE_TAG}
    ports:
      - 3003:3003
    networks:
      - internal
    depends_on:
      - mongo
    environment:
      # MIDDLEWARE_HOST: 127.0.0.1
      MIDDLEWARE_PORT: 3003
      BUSINESS_LOGIC_HOST: business-logic
      BUSINESS_LOGIC_PORT: 3000
      USER_HOST: user-service
      USER_PORT: 3001
      MONGO_USER: ${MONGO_USER}
      MONGO_PASSWORD: ${MONGO_PWD}
      MONGO_HOST: mongo
      MONGO_PORT: 27017
      MONGO_DATABASE: ${MONGO_DB}
      DEBUG: true

  cadvisor:
    container_name: cadvisor
    image: gcr.io/cadvisor/cadvisor:latest
    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
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - "./prometheus.yml:/etc/prometheus/prometheus.yml"
    privileged: true
    depends_on:
      - cadvisor

  grafana:
    container_name: grafana
    image: grafana/grafana:latest
    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:
  grafana-data:

An Appendix Chapter (Optional)

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.

An Appendix Chapter (Optional)

...


  1. 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.↩︎