U jednom od prethodnih članaka smo vidjeli da je kreiranje "fat" web aplikacije, odnosno aplikacije koja se sastoji od web servera i svih potrebnih biblioteka, izuzetno jednostavno ukoliko koristimo prave alate, u ovom slučaju Spring Boot. Sada ćemo postojeće znanje proširiti kreiranjem web endpointa za REST komunikaciju sa aplikacijom. Konkretno, kreirat ćemo nekoliko endpointa uz pomoć kojih ćemo koristeći REST semantiku čitati, kreirati, ažurirati i brisati objekte iz baze podataka.

Prvi korak u dizajniranju REST aplikacije je uobičajeno identifikacija resursa ili objekata aplikacije. Obzirom da želimo proširiti primjere na kojima smo već radili, naš primarni resurs nastavlja biti Movie objekat.

Nakon ove identifikacije, moramo donijeti odluku na koji način predstaviti navedeni resurs. Izbor formata naravno najviše ovisi od API klijenata, npr. u slučaju da se radi o "internim" korisnicima API-ja možemo izabrati samo JSON reprezentaciju, a za "vanjske" klijente uobičajeno želimo podržati više standardnih formata, uključujući JSON i XML. Za potrebe našeg primjera, format kojeg želimo podržati je JSON, koji omogućava jednostavnu reprezentaciju pojedinačnih i nizovnih objekata.

U ovom trenutku možemo dizajnirati URI endpointe. Bazni URI aplikacije će biti http://localhost:8080. Na ovaj bazni URI ćemo po konvenciji dodati naziv resursa, u množini, čime dobijamo http://localhost:8080/movies. Konačno, individualnim resursima ćemo pristupati tako što u naš URI dodamo i identifikator resursa, npr. http://localhost:8080/movies/1, i http://localhost:8080/movies/5. Zajednički način identifikacije možemo predstaviti uz pomoć uri varijable: http://localhost:8080/movies/{movieId}.

Konačno, nakon što smo identificirali resurse aplikacije, odabrali format prezentacije te definisali URI endpointe, u posljednjem koraku trebamo odabrati HTTP akcije uz pomoć kojih ćemo omogućiti interakciju između klijenta i resursa, putem navedenih endpointa. Za rad sa kolekcijama želimo obično omogućiti GET i POST akciju, a za pojedinačne resurse uobičajeno definišemo akcije GET, PUT i DELETE. GET akcija naravno služi za čitanje i dohvatanje filmova, a metode POST, PUT i DELETE za unos, ažuriranje i brisanje filmova.  

HTTP metod

Endoint

Status uspjeh

Status neuspjeh

Opis

GET

/movies

200

500

Vraća listu svih filmova

POST

/movies

201

500

Kreira novi film

PUT

/movies/{movieId}

200

404 / 500

Ažurira postojeći film

DELETE

/movies/{movieId}

200

404 / 500

Briše postojeći film

GET

/movies/{movieId}

200

404 / 500

Vraća detalje jednog filma

 

Obratiti pažnju da smo definirali i moguće statuse u slučaju uspjeha ili neuspjeha. Tako npr. ukoliko želimo ažurirati detalje postojećeg filma, u slučaju da film sa navedenom identifikacijom ne postoji, dobićemo status 404 (Not Found), a u slučaju drugih grešaka status 500.

Sada možemo krenuti u implementaciju naše aplikacije. Arhitektura aplikacije izgleda ovako:

 


Za čuvanje podataka koristimo memorijsku varijantu H2 baze podataka, a kao interfejs za rad sa podacimo smo koristili spring-data-jpa projekat. Obzirom da smo ovaj dio aplikacije pokrili i detaljnije objasnili u ranijim tekstovima, ovdje ćemo se bazirati na API komponentu. Ova komponenta je u osnovi REST kontroler, odnosno klasa koja izlaže definisane endpointe prema vanjskom svijetu i obavlja svoj posao komunicirajući sa repozitorijem baze podataka. Početna implementacija kontrolera izgleda ovako:

@RestController
public class MovieRestController {

}


Anotacija @RestController znači da će klasa biti registrovana kao Spring komponenta (bean) i dostupna u njegovom aplikacionom kontektsu, te da će Springov DispatcherServlet moći prosljeđivati URL zahtjeve ovoj komponenti. Dodatno ova anotacija osigurava da se povratne vrijednosti metoda unutar navedene klase koriste u HTTP odgovorima na klijentske zahtjeve. Drugim riječima, DispatcherServlet sada zna kome da proslijedi HTTP zahtjeve i kako da konstruiše odgovarajući odgovor.

Prva operacija koju želimo implementirati jeste GET operacija za dohvatanje svih filmova. Ukoliko unutar klase omogućemo ubacivanje repozitorijske klase za komunkaciju sa bazom podataka (vidjeti prethodne članke), jedan od načina za implementaciju ove operacije može biti ovaj:

@GetMapping("/movies")
public ResponseEntityIterable> getAllMovies() {
   return new ResponseEntity>(movieRepository.findAll(), HttpStatus.OK);
}

 

Anotacija @GetMapping HTTP web zahtjev na endpointu /movies na metod koji se zove getAllMovies(). Treba primjetiti da metod vraća objekat u kojem se nalazi i payload (skup filmova) i HTTP status (OK = 200). Ako pokrenemo aplikaciju, navedenu operaciju možemo testirati sa curl alatom ili nekim od mnogobrojnih REST klijenata.

curl -X GET http://localhost:8080/movies

 

Rezultat ovog poziva je slijedeći:

[{"id":1,"title":"Gori Vatra","duration":98}]

 

Postavlja se pitanje na koji način je vraćeni payload konvertovan u JSON format. Zaslugu za ovaj zadatak preuzima automatski konfigurisani HTTP message converter koji uz pomoć Jackson biblioteke vrši konverziju u JSON.

Na sličan način možemo implementirati i POST akciju. Razlika je što sada očekujemo da dobijemo atribute filma u obliku JSON dokumenta unutar tijela HTTP poruke. U tu svrhu ćemo koristiti @RequestBody anotaciju:

@PostMapping("/movies")
public ResponseEntity createMovie(@RequestBody Movie movie) {
   movieRepository.save(movie);
   return new ResponseEntity(movie, HttpStatus.CREATED);
}

 

Sada možemo okinuti slijedeći request:

curl -H "Content-Type: application/json" -X POST http://localhost:8080/movies -d
 '{"title": "Ničija zemlja", "duration":92}'

 

Obzirom da se radi o POST metodi. web zahtjev se mapira na java metod createMovie(movie), a JSON iz zahtjeva se automatski konvertuje u objekat klase Movie.

Akcija za dohvatanje detalja jednog filma uzgleda ovako:

@GetMapping("/movies/{movieId}")
public ResponseEntity getMovie(@PathVariable int movieId) {
   Movie movie = movieRepository.findOne(movieId);
   return movie != null ? new ResponseEntity>(movie, HttpStatus.OK)
      : new ResponseEntity>(HttpStatus.NOT_FOUND);
}


Obzirom da je id filma dio URL puta, koristimo tzv. path varijablu, unutar @GetMapping anotacije označene sa {movieId}, te kao argument metode uz pomoć anotacije @PathVariable. Drugim riječima, ako kreiramo GET zahtjev na http://localhost:8080/movies/5, bit će pozvan metod getMovie(5). Treba primjetiti da u slučaju da film sa datim id-em ne postoji, metod vraća prazan payload i status 404 (NOT FOUND):

 

curl -v -X GET http://localhost:8080/movies/5

* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /movies/5 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.47.0
> Accept: */*
> 
 HTTP/1.1 404 
 Content-Length: 0

 

Ažuriranje filma sadrži sličnu logiku, uz razliku što sada koristimo i path varijablu (identifikacija filma) i JSON body (ažurirani dokument)

@PutMapping("/movies/{movieId}")
public ResponseEntity updateMovie(@PathVariable int movieId, @RequestBody Movie data) {
   Movie movie = movieRepository.findOne(movieId);
   if (movie == null)
      return new ResponseEntity>(HttpStatus.NOT_FOUND);
   data.setId(movieId);
   movieRepository.save(data);
   return new ResponseEntity>(data, HttpStatus.OK);
}

 

curl -H "Content-Type: application/json" -X PUT http://localhost:8080/movies/1 -d
 '{"title": "Gori vatra", "duration":100}'


Konačno, u slučaju brisanja filmova koristimo @DeleteMapping za DELETE HTTP metod:

@DeleteMapping("/movies/{movieId}")
public ResponseEntity deleteMovie(@PathVariable int movieId) {
   Movie movie = movieRepository.findOne(movieId);
   if (movie == null)
      return new ResponseEntity>(HttpStatus.NOT_FOUND);
   movieRepository.delete(movie);
   return new ResponseEntity>(HttpStatus.OK);
}

 

curl -H "Content-Type: application/json" -X DELETE http://localhost:8080/movies/1

 

Vidjeli smo da je kreiranje REST servisa izuzetno lagano, ali ako želimo još pojednostaviti implementaciju, možemo koristiti spring-data-rest projekat. Ovaj projekat je izgrađen direktno nad spring-data projektom i omogućava automatsko kreiranje različitih REST endpointa na osnovu definisanih repozitorija. Sve što je potrebno uraditi jeste dodati maven dependency spring-boot-starter-data-rest i definisati bazni path:

spring.data.rest.basePath=/rest

 

Nakon ovoga, spring-data-rest će kreirati endpoint za svaki repozitorij u aplikaciji. Npr. posmatrajmo slijedeći repozitorij:

@Repository
public interface MovieRepository extends CrudRepository {
}


Za ovaj repozitorij će biti kreirani endpointi za čitanje, unos, ažuriranje i brisanje podataka. Za konstruisanje endpointa uzima se ime domenskog objekta sa kojim manipulišemo (Movie) i pretvara se u množinu. Primjeri generisanih endpointa su http://localhost:8080/rest/movies, http://localhost:8080/rest/movies/{movieId} itd. Naravno, ovo je samo vrh ledenog brijega jer spring-data-rest omogućava mnogo kompleksnije scenarije i kastomizacije, ali je dovoljno da uvidimo jednostavnost i korisnost upotrebe ove sjajne biblioteke.

Do narednog čitanja,

Almir Pehratović, Infobip BH d.o.o.

1/1