8. týždeň

Úloha 6 - Webové REST služby

Ciele

  1. Upraviť závislosti projektu gamestudio pre umožnenie ďalšieho postupu v rámci cvičení.
  2. Vytvoriť triedu pre spustenie serverovej časti.
  3. Implementovať služby sprístupňujúce servisné komponenty klientovi prostredníctvom REST rozhrania.
  4. Vytvoriť služby klienta využívajúceho vytvorené REST rozhranie.
  5. Spustiť serverovú a klientskú časť projektu.

Úvod

Na dnešnom cvičení vykonáte v projekte úpravy, aby Gamestudio fungovalo ako distribuovaná klient-server aplikácia.

Aplikácia servera bude poskytovať funkcionalitu pre doplnkové služby Score, Rating a Comment a bude komunikovať s databázou. Klientska aplikácia bude spúšťať hru a pre získanie a ukladanie údajov služieb Score, Comment a Rating bude posielať dopyty na server prostredníctvom REST.

Postup

Krok 1

Úloha 1.1

V konfiguračnom súbore pom.xml pre nástroj Maven doplňte novú závislosť, ktorá umožní prácu s webovými službami v rámci Spring projektu.

Medzi existujúce závislosti projektu (dependencies) doplňte nasledovné:

<dependencies>

    ... //predtým pridané závislosti ponechať nezmenené

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
</dependencies>

Poznámka

Pri pridaní závislosti je potrebné aktívne pripojenie na internet, aby sa stiahli potrebné knižnice. V prostredí sa vám môže vpravo dolu objaviť notifikácia Maven pre potvrdenie aktualizácie závislostí projektu. Túto aktualizáciu je možné vyvolať aj manuálne cez kontextové menu (po kliknutí pravým tlačidlom myši) Maven -> Reload project alebo cez panel Maven a tlačidlo Reload All Maven Projects.

Úloha 1.2

Upravte metódu main() v triede SpringClient, aby sa pri spustení klientskej časti projektu nespúšťal zároveň aj webový server, keďže ten sa bude spúšťať v inej časti.

Pôvodnú inicializáciu aplikácie v metóde main(), ktorá vyzerala takto:

public static void main(String[] args) {
    SpringApplication.run(SpringClient.class, args);
}

upravíme týmto spôsobom:

public static void main(String[] args) {
    new SpringApplicationBuilder(SpringClient.class).web(WebApplicationType.NONE).run(args);
}

Krok 2

V tomto kroku implementujete kód pre spúšťanie serverovej časti aplikácie.

Úloha 2.1

V balíku sk.tuke.gamestudio.server pridajte hlavnú triedu pre spúšťanie serverovej časti aplikácie, s názvom GameStudioServer.

Implementácia triedy GameStudioServer zatiaľ bude obsahovať metódu main(), ktorá je veľmi podobná metóde v triede SpringClient pred úpravami z predošlého kroku:

package sk.tuke.gamestudio.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
@Configuration
@EntityScan("sk.tuke.gamestudio.entity")
public class GameStudioServer {
    public static void main(String[] args) {
        SpringApplication.run(GameStudioServer.class, args);
    }
}

Poznámka

Anotácia @EntityScan umožňuje definovať umiestnenie balíka s entitnými triedami, keďže je tento balík umiestnený na inej úrovni projektovej štruktúry, než na ktorej je umiestnená trieda GameStudioServer. Podstatné to bude pre ďalšie časti implementácie servera.

Úloha 2.2

Otestujte funkčnosť serverovej časti spustením triedy GameStudioServer.

Po pridaní novej závislosti do projektu (Krok 1) sa stiahla a nastavila aj knižnica pre webový server Tomcat. Tento server zvyčajne beží na porte 8080.

Pri správnom spustení servera sa v termináli objaví na konci správa obsahujúca Started GameStudioServer in ... seconds. Pri otvorení webového prehliadača na adrese localhost:8080 zatiaľ dostanete ako odpoveď kód 404, keďže na danej adrese ešte nie je dostupný žiaden obsah. Server bol ale schopný poslať túto odpoveď na požiadavku, čo znamená, že je spustený.

Upozornenie

Pokiaľ vám v systéme už beží iná služba na tom istom porte, môže nastať konflikt a spustenie aplikácie môže skončiť chybou. V takom prípade je riešením dočasné zastavenie služby, ktorá využíva port 8080, alebo zmena portu pre server Tomcat.

Po tejto úlohe bude vaša aplikácia rozdelená na klientsku a serverovú časť. Pre zjednodušenie je ale implementácia oboch častí v jednom spoločnom projekte.

Krok 3

V tomto kroku implementujete REST službu, ktorá sprístupní servisný komponent cez webový protokol HTTP. Implementácia je pomerne priamočiara. Pre službu Score budú k dispozícii 2 metódy: jedna na získanie najlepších skóre (GET), druhá na uloženie skóre (POST).

Úloha 3.1

V balíku sk.tuke.gamestudio.server vytvorte balík webservice, ktorý bude obsahovať implementáciu webových služieb. V balíku vytvorte a implementujte triedy jednotlivých služieb.

Implementácia metód REST služby bude jednoduchá, keďže sa v nej len využijú metódy injektovanej implementácie služby ScoreService. O všetko ostatné sa postará Spring.

Trieda pre implementáciu služby Score bude pomenovaná ScoreServiceRest a bude vyzerať nasledovne:

package sk.tuke.gamestudio.server.webservice;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import sk.tuke.gamestudio.entity.Score;
import sk.tuke.gamestudio.service.ScoreService;

import java.util.List;

@RestController
@RequestMapping("/api/score")
public class ScoreServiceRest {
    
    @Autowired
    private ScoreService scoreService;

    @GetMapping("/{game}")
    public List<Score> getTopScores(@PathVariable String game) {
        return scoreService.getTopScores(game);
    }

    @PostMapping
    public void addScore(@RequestBody Score score) {
        scoreService.addScore(score);
    }
}

Poznámka

V rámci implementácie služieb pre web nebudeme sprístupňovať metódu pre reset(). Tá preto nebude súčasťou implementovanej triedy.

V uvedenom príklade sú dôležité anotácie, ktoré REST službu definujú:

  • @RestController - označuje, že komponent je REST služba,
  • @RequestMapping("/api/score") - URL adresa služby na serveri, na ktorej bude služba sprístupnená,
  • @PostMapping - metóda označená anotáciou bude reprezentovať REST metódu typu POST,
  • @GetMapping - metóda označená anotáciou bude reprezentovať REST metódu typu GET,
  • @PathVariable - označuje parameter metódy, ktorého hodnota bude naplnená z časti URL, v našom prípade to je názov hry, napr. z URL /api/score/mines bude hodnota parametra mines,
  • @RequestBody - označuje parameter metódy, ktorý bude naplnený z obsahu dopytu.

Úloha 3.2

Doplňte implementáciu triedy GameStudioServer o komponenty služieb, ktoré budú využívané vo webových službách z predošlého kroku.

Príklad pre službu ScoreService:

package sk.tuke.gamestudio.server;

import org.springframework.boot.SpringApplication;
// ... dalsie importy

@SpringBootApplication
@Configuration
@EntityScan("sk.tuke.gamestudio.entity")
public class GameStudioServer {
    public static void main(String[] args) {
        SpringApplication.run(GameStudioServer.class, args);
    }
    
    @Bean
    public ScoreService scoreService() {
        return new ScoreServiceJPA();
    }
}

Krok 4

Server môžete pred ďalším postupom otestovať. Spustenie sa vykonáva spustením hlavnej triedy servera GameStudioServer. Server sa spustí na porte 8080.

Upozornenie

Na porte 8080 nesmie v tom čase bežať iná služba, inak sa server nespustí.

Úloha 4.1

Spustite server a vyskúšajte vytvorenú REST službu.

Pripravte si HTTP dopyty pre vyskúšanie služieb. Napríklad, GET vyskúšajte prostredníctvom prehliadača na URL adrese http://localhost:8080/api/score/mines.

Poznámka

Pre testovanie REST služieb si môžete vytvoriť súbor s koncovkou .http, do ktorého zapíšete jednotlivé dopyty a môžete ich tak spúšťať priamo vo vývojovom prostredí IntelliJ IDEA.

IntelliJ IDEA - HTTP Requests
Obr. 1: IntelliJ IDEA - HTTP Requests

Úloha 4.2

Podľa vzoru ScoreServiceRest.java vytvorte implementácie REST služieb pre zvyšné dva servisné komponenty: CommentServiceRest.java a RatingServiceRest.java.

Úloha 4.3

Prostredníctvom HTTP požiadaviek otestujte fungovanie zvyšných služieb.

Krok 5

V tomto kroku vytvoríme službu REST klienta na strane klientskej časti aplikácie GameStudio s využitím už vytvoreného REST webového rozhrania. Príklad bude opísaný na službe Score.

Úloha 5.1

V balíku sk.tuke.gamestudio.service vytvorte novú triedu ScoreServiceRestClient reprezentujúcu klienta vytvoreného REST rozhrania. Trieda bude implementovať rozhranie ScoreService.

Pre použitie REST metód využijeme objekt typu RestTemplate, ktorý môže byť injektovaný zvonku, pri využití anotácie @Autowired.

package sk.tuke.gamestudio.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;
import sk.tuke.gamestudio.entity.Score;

import java.util.Arrays;
import java.util.List;

public class ScoreServiceRestClient implements ScoreService {
    private final String url = "http://localhost:8080/api/score";

    @Autowired
    private RestTemplate restTemplate;
    //private RestTemplate restTemplate = new RestTemplate();

    @Override
    public void addScore(Score score) {
        restTemplate.postForEntity(url, score, Score.class);
    }

    @Override
    public List<Score> getTopScores(String gameName) {
        return Arrays.asList(restTemplate.getForEntity(url + "/" + gameName, Score[].class).getBody());
    }
    
    @Override
    public void reset() {
        throw new UnsupportedOperationException("Not supported via web service");
    }
}

Pri injektovaní objektu RestTemplate zvonku bude potrebné ešte v triede SpringClient vytvoriť patričnú metódu, označenú anotáciou @Bean, ktorá vráti objekt RestTemplate.

Krok 6

V tomto kroku využijeme vytvorených REST klientov v klientskej časti aplikácie GameStudio.

Úloha 6.1

Zmeňte definície servisných komponentov v triede SpringClient na REST.

Príklad pre ScoreService bude vyzerať nasledovne:

 @Bean
 public ScoreService scoreService() {
     //return new ScoreServiceJPA();
     return new ScoreServiceRestClient();
 }

Obdobným spôsobom upravte aj ostatné servisné komponenty.

Úloha 6.2

Pridajte k triede SpringClient anotáciu @ComponentScan s parametrom excludeFilters pre zabránenie konfliktov s definíciami komponentov, ktoré sú v balíku serverovej časti aplikácie.

Keďže máme pre zjednodušenie klientsku aj serverovú aplikáciu v spoločnom projekte, potrebujeme zabrániť konfliktom pri skenovaní dostupných komponentov.

Anotáciu použite nasledovne:

...
@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX,
        pattern = "sk.tuke.gamestudio.server.*"))
public class SpringClient {
    ...
}

Úloha 6.3

Overte funkčnosť klientskej aplikácie.

Upozornenie

Keďže už je klientska a serverová časť oddelená, musíte mať vždy pred spustením klienta spustený aj server.

Spustite klienta aplikácie spustením jeho hlavnej triedy SpringClient a skúste si zahrať hru. Pri využití servisných služieb klient pošle HTTP požiadavku na server, ten pri spracovaní požiadavky bude komunikovať s databázou, a pošle odpoveď prípadne požadované údaje naspäť klientovi.

Krok 7

Úloha 7.1

Nahrajte vašu implementáciu do vášho repozitára na GitLab-e. Projekt môžete ďalej priebežne aktualizovať. Pred ďalším cvičením sa uistite, že máte v repozitári vaše aktuálne súbory. Zároveň si pripravte otázky, ktoré by ste na cvičení chceli vyriešiť.

Krok 8

Bonus: A teraz si za odmenu môžete dať virtuálny koláčik (prezentácia What makes a delicious game).

Zdroje