Architettura di una applicazione SpringBoot
Con SpringBoot è possibile realizzare molti tipi di applicazioni:
- WebServices e Web sites anche tramite microservizi;
- Applicazioni di grandi dimensioni per la gestione di banche dati (server to server);
- Applicazioni per l’elaborazione automatica di dati (ETL, migrazioni, ecc.);
- Applicazioni desktop (con Electron)
- Sistemi real-time (per esempio per IoT o domotica)
- Applicazioni cloud serverless (come Lambda)
In queste lezioni ci occuperemo principalmente di Web Service, un tipo di Web Application.
Struttura applicativa di una Web Application
Un Web Service è una Web Application, ovvero una applicazione lato server che ha il ruolo di backend di un Web Server. A sua volta la Web Application si appoggia, per la persistenza dei dati, ad un sistema di database esterno che permette di memorizzare e ricercare i dati su un database.
Lo schema è dunque il seguente:

Il Web Server in realtà può essere sia interno che esterno all’applicazione SpringBoot:
- se interno, SpringBoot compilerà un Jar che conterrà quindi entrambi gli applicativi, sia il Web Server (Apache Tomcat) che la Web Application associata. Questa soluzione è indicata per avere soluzioni all-in-one.
- es esterno, SpringBoot crea un War con la sola Web Application, che andrà poi installata su un Web Server esistente. Questa soluzione è quella indicata quando dietro allo stesso Web Server sono collegate diverse Web Application.
Architettura di un Web Service
Un Web Service Springboot è di norma con una una architettura a 3 layer, dove ogni layer ha una precisa responsabilità applicativa:
- i controllers definiscono le URL ed i servizi erogati, si occupano di gestire le richieste HTTP, chiamare il layer sottostante per eseguire le operazioni, e costruire la risposta per il client. Si occupano poi della sicurezza.
- i services eseguono la business logic, cioè la logica ad alto livello dell’applicazione, e accedono ai dati del livello sottostante.
- Il model/repository invece gestisce i dati e le loro relazioni, insieme alla logica necessaria per caricarli e salvarli nel database, attraverso la libreria JPA/Hibernate di Java EE (esistono comunque delle alternative che consentono l’accesso diretto al database).
Sono poi previsti dei moduli di supporto:
- le utilities sono delle classi di supporto alla business logic
- analogamente, gli helpers sono classi di supporto ai controllers
Questo schema può essere riassunto graficamente in questo modo (le frecce indicano le dipendenze):

I dati sono rappresentati sia del Model, che rappresenta le entità del dominio di informazione dell’applicazione, e dai Data Transfer Object, oggetti senza funzioni che sono utilizzati per trasferire dati attraverso i layers. Questo tipo di struttura è utilizzata nella maggioranza dei servizi web, con eventuali varianti.
Dependency Injection e Inversion of Control
Come anticipato nell’introduzione SpringBoot si basa su Spring. Uno dei componenti più importanti di Spring è l’IoC container, che implementa due principi di progettazione, l’Inversion of Control (IoC) e la Dependency Injection (DI).
IoC
In Java e nella programmazione ad oggetti quando una classe A ha bisogno di una classe B, importa il package della classe B e crea la classe B con il suo costruttore. Questo meccanismo è molto semplice ed efficace nei piccoli progetti, ma diventa molto complicato quando le applicazioni aumentano di dimensioni, perchè B potrebbe richiedere risorse che A non possiede, e quindi A dovrebbe importare tutte le dipendenze di B, che a loro volta potrebbero avere altre dipendenze. Per evitare questo problema sono state proposte diverse soluzioni, ma la più efficace è l’Inversion of Control, ovvero demandare ad un componente esterno la creazione di questi oggetti, chiamato IoC container.
DI
La Dependency Injection è un tipo di meccanismo di IoC container, che in Spring è implementato dalla classe ApplicationContext. L’ApplicationContext analizza le classi del progetto a runtime attraverso le loro annotazioni e crea un albero di dipendenze delle classi. Quando la classe A richiede la classe B, l’ApplicationContext “risale l’albero” delle dipendenze e crea la classe B con tutte le dipendenze e la fornisce ad A.
E’ importante capire che ApplicationContext non si limita a fornire una istanza di B ad A, ma può essere configurato per disaccoppiare la dipendenza. A invece di dipendere in modo “rigido” da una classe B, può dipendere da una interfaccia InterfaceB. E’ compito dell’ApplicationContext individuare ed istanziare la classe della gerarchia di B più adatta.
Bean
Vediamo concretamente come funziona.
Per definire le classi che sono gestite dall’Application Context, queste sono annotate con la keyword @Bean (o sue specializzazioni, che vedremo sotto). Un Bean è quindi considerata una classe “managed” ed entra a far parte del grafo delle dipendenze possibili, se richiesto da qualche parte nell’applicazione.
Vediamo con un esempio. Creiamo questa classe Bean:
public interface NotificationService {
void notify(String message);
}
@Bean
public class CustomerService implements NotificationService {
public void notify(String message {
...
}
}E poi la classe che la utilizza:
@Bean
public class CustomerRelationManager {
private NotificationService service;
public CustomerRelationManager(NotificationService service) {
this.service = service;
}
}Quando viene avviata l’applicazione, l’ApplicationContext cerca una classe concreta che implementa l’interfaccia, la istanzia e la inietta dove è richiesta. La classe che utilizza quella interfaccia non solo non si preoccupa di creare quella istanza, ma non sa nemmeno quale istanza concreta sia.
Nei progetti più semplici le interfacce spesso non sono necessarie perché non serve un disaccoppiamento così forte, è quindi sufficiente indicare la dipendenza verso una specifica classe.
Lo vediamo con un esempio. Nella nostro Todolist creiamo una classe controller che necessita per funzionare della classe TodoService.
@Service
public class MyService {
...
}
public class MyController {
private MyService service;
public MyController(MyService service) {
this.service = service;
}Spring prevede una gerarchia di annotazioni Bean più specialistiche
- @Component: indica un generico componente di un WebService
- @Service: indica un @Component che si occupa della business logic
- @Repository: indica un @Component che si occupa della persistenza su database
- @Controller: indica un @Component che definisce le chiamate al WebService (le singole URL sono associate ad uno specifico servizio).
Come vedremo il sistema delle annotazioni è in realtà molto più vasto, e si applica in molti ambiti, per aggiungere funzionalità alle classi e ai metodi, e ridurre la quantità di codice da scrivere. Non è utilizzato solo da Spring ma anche da molte librerie della specifica Java EE, come JPA, che vedremo nella prossima lezione.
