Implementare REST API con Spring Boot

In questo post vediamo come iniziare a costruire un semplice REST web service utilizzando gli strumenti messi a disposizione da Spring MVC e Spring Boot.

Il nostro servizio, creato appositamente per l’occasione, permetterà di gestire una fantastica SuperRubricaREST! (vi ricorda qualcosa?) :-D

Il codice completo del progetto lo trovate qui.

Breve introduzione a REST

Prima di mettere mano al codice, introduciamo alcuni concetti chiave utili (soprattutto a chi sente parlare di REST per la prima volta) per comprendere il progetto che andremo a sviluppare.

REST (REpresentational State Transfer) è un modello architetturale, descritto per la prima volta nel 2000 da Roy Fielding, che permette la comunicazione/interoperabilità tra sistemi remoti, tipicamente connessi ad Internet.

Negli ultimi anni l’architettura REST ha progressivamente preso piede e, in molti casi, soppiantato il più anziano e consolidato protocollo SOAP; questo per via della sua “leggerezza” e relativa semplicità d’applicazione.

Non mi soffermo a descrivere le differenze tra REST e SOAP, né a confrontare pro e contro dei due sistemi; qualcun altro sa parlarne molto meglio di me… Elenco invece alcune delle caratteristiche di REST che ci interessano per capire come implementare API che rispettino questo approccio:

  • un servizio REST espone risorse, non metodi, come invece avviene in un servizio basato su SOAP;
  • il formato comunemente utilizzato per rappresentare le risorse esposte è il JSON, ma nulla vieta di utilizzare altri media type come ad esempio XML;
  • il modello REST viene tipicamente implementato tramite il protocollo HTTP; i metodi/verbi HTTP vengono utilizzati per definire le azioni da compiere sulle risorse;
  • un web service REST è intrinsecamente stateless, per cui dimenticate il concetto di sessione; il server è privo di memoria ed ogni richiesta verrà gestita in maniera indipendente.

Detto ciò, è il momento di scoprire come Spring ci aiuta a tradurre in codice questi concetti…

Per cominciare…

Come prima cosa, creiamo un nuovo progetto Spring Boot utilizzando il buon vecchio Spring Initializr.

Per il momento possiamo includere la sola dipendenza Web (il pacchetto corrispondente è spring-boot-starter-web).

Scarichiamo lo zip e apriamo il progetto col nostro IDE preferito.

Bene, possiamo cominciare!

Aggiungiamo un RestController

In un web service REST le risorse sono identificate da URI; le richieste HTTP dirette verso questi indirizzi permettono di interagire con le risorse esposte e di effettuare su di esse diverse operazioni (ad esempio, CRUD).

Come per una classica applicazione web, anche in questo caso Spring permette di gestire le richieste inviate al server attraverso degli oggetti di tipo Controller.

Nel caso di un web service, però, abbiamo la possibilità di sfruttare una tipologia di componente specifica che ci aiuta ulteriormente nell’implementazione dei nostri resource controller. Questi componenti sono identificati dall’annotazione @RestController:

@RestController
@RequestMapping("/contacts")
public class ContactController {

    @Autowired
    private ContactService contactService;

    @GetMapping
    public List<Contact> getAll() {
        return contactService.getList();
    }

    @GetMapping("/{id}")
    public Contact getById(@PathVariable("id") long id) {
        return contactService.getById(id);
    }

    @PostMapping
    public Contact create(@RequestBody Contact contact) {
        return contactService.create(contact);
    }

    @PutMapping("/{id}")
    public Contact update(@PathVariable("id") long id, @RequestBody Contact contact) {
        return contactService.update(id, contact);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable("id") long id) {
        contactService.delete(id);
    }

}

Alcune cose da notare nel codice appena riportato:

  • l’annotazione @RestController rappresenta una specializzazione di @Controller che, in un colpo solo, permette di annotare tutti gli handler method del Controller con @ResponseBody;
  • i metodi di ContactController non restituiscono stringhe (in questo caso non c’è nessuna pagina da visualizzare…); l’annotazione @ResponseBody, permette infatti di “incorporare” il valore di ritorno del metodo nel corpo della risposta HTTP;
  • allo stesso modo, l’annotazione @RequestBody permette di effettuare il binding tra il corpo della richiesta e l’argomento di un handler;
  • il mapping delle richieste sfrutta in modo “coerente” i verbi previsti dal protocollo HTTP per definire le operazioni (cambiamenti di stato) da effettuare sulle risorse.

Rappresentare le risorse

Lanciamo l’applicazione e utilizziamo un client HTTP, come ad esempio Postman, per effettuare qualche test sul nostro servizio.

Inviando una GET request all’URI localhost:8080/contacts/1 otterremo qualcosa del genere:

{
    "id": 1,
    "firstName": "Mario",
    "lastName": "Rossi",
    "phone": "3332345678",
    "email": "[email protected]"
}

Come dicevamo all’inizio di questo post, l’architettura REST non pone vincoli riguardo al formato da utilizzare per rappresentare le risorse.

Spring Boot utilizza JSON come formato di default per serializzare/deserializzare i nostri oggetti (tramite la libreria Jackson). Dunque, se non specifichiamo un valore per gli header Content-Type e Accept delle nostre richieste, il framework assegnerà di default il media type application/json.

Possiamo in ogni caso utilizzare altri formati per rappresentare le risorse del servizio. Supponendo ad esempio di voler supportare anche il formato XML, ci basterà aggiungere alle dipendenze del progetto questo pacchetto:

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

In questo modo, specificando il media type application/xml negli header indicati precedentemente, potremo inviare e ricevere oggetti con questo formato:

<Contact>
  <id>1</id>
  <firstName>Mario</firstName>
  <lastName>Rossi</lastName>
  <phone>3332345678</phone>
  <email>[email protected]</email>
</Contact>

Gradi di RESTfulness

È importante tenere a mente che REST non è uno standard, il che costituisce sia un punto di forza, sia un punto debole di questo stile di architettura.

Rappresenta un punto di forza perché permette allo sviluppatore di adattare il modello e la sua implementazione alle reali esigenze di progetto. D’altro canto, la non-standardizzazione genera una proliferazione incontrollata di approcci e stili che, indubbiamente, possono disorientare gli utilizzatori (client) di questi servizi.

Martin Fowler, in un articolo di ormai diversi anni fa, riassumeva alcune regole del cosiddetto Richardson Maturity Model. Questo modello prevede diversi livelli di compliance all’approccio REST e, se ci fate caso, la nostra API si attesta all’incirca al livello 2 (visto che abbiamo saltato a piè pari il concetto di HATEOAS).

Ovviamente, anche in questo caso, sono i requisiti di progetto che ci suggeriscono fin dove dobbiamo spingerci.

Personalmente credo che un approccio quanto più pragmatico e non necessariamente legato a “dogmi” stilistici, sia l’approccio migliore allo sviluppo di servizi che generino reale valore.

In conclusione

Abbiamo visto come iniziare a sviluppare un semplice REST service con Spring Boot; nei prossimi articoli di questa serie cercherò di aggiungere un po’ di “sostanza” al progetto esaminando altri aspetti interessanti e spero utili.

Avremo sicuramente modo di tornare sull’argomento REST anche da un punto di vista più ad alto livello, magari per parlare di linee guida e best practices architetturali.

A presto,

David