Jak stworzyć RestController w Spring Boot?

Czym jest kontroler?

Abyśmy w ogóle mogli przejść do rozmowy o rest kontrolerach, na poczatek określmy sobie czym jest kontroler.

Kontroler w aplikacji webowej jest utożsamiany z miejscem, gdzie mamy możliwość rozmowy aplikacji jako użytkownicy przy użyciu zapytań HTTP.

Owe miejsca są wystawiane przez serwer aplikacyjny pod konkretnymi adresami URL. Jeśli użytkownik wyśle zapytanie pod zdefiniowany wcześniej end-point to automatycznie jego zapytanie jest procesowane przez nasz kod, a na sam koniec zostaje zwracana odpowiedź.

I w teorii tak to wygląda, przejdźmy sobie teraz do krótkiego wstępu teoretycznego o restowych kontrolerach.

Co to jest rest?

Zanim zaczniemy rozmawiać o restach w Spring Boocie warto zacząć od tego co się w ogóle kryje pod tym tajemnicznym pojęciem.

REST (representational state transfer) jest sposobem komunikacji opartym o protokół HTTP. Polega on na unstandaryzowaniu sposobu wymiany danych pomiędzy klientem, a serwerem – wymiana danych z założenia ma być prosta i łatwa w implementacji.

Dodatkowym punktem jest to, że serwer nic nie musi wiedzieć o kliencie i to klient decyduje o sposobie konsumpcji otrzymanych danych. Dużą zaletą tego rozwiązania jest to, że klient może być często modyfikowany bez konieczności zmian na serwerze. Przykładem takiego klienta może być aplikacja webowa napisana np. w ReactJS lub Angularze.

Wiesz już, że REST jest oparty o HTTP, więc sposób tutaj nie wspomnieć o metodach HTTP. Rest web serwis wykorzystuje cztery z nich:

  • GET;
  • POST;
  • PUT;
  • DELETE.

Każda z nich ma swoje przeznaczenie i powinna być używana w odpowiednim kontekście. Po krótce opiszę przeznaczenie każdej z nich:

GET

Służy do odpytywania web serwis o zasoby, przykładem takiego zapytania może być:

Drogi web serwisie, podaj mi listę użytkowników

Co moglibyśmy opisać takim urlem:

http://app.com/users

Nasze zapytanie mogłoby być również sprecyzowane:

Drogi reście, podaj mi użytkownika o ID równym 1

I wtedy URL wyglądałby np. tak:

http://app.com/users/1

POST

Metoda POST jest wykorzystywana do tworzenia nowych zasobów na serwerze, w przeciwieństwie do poprzedniej metody GET może zawierać ona tzw. payload czyli body zapytania. W body możemy np. umieścić dane o tworzonym użytkowniku.

Drogi serwisie, utwórz mi użytkownika o loginie Adam i haśle admin.

Takie zapytanie w curl wyglądałoby wtedy tak:

curl --request POST --data '{\"login\": \"Adam\", \"password\": \"admin\"}' http://app.com/users

PUT

Metoda PUT jest bardzo podobna do metody POST, jednak jest używana w przypadku, gdy chcemy edytować istniejący zasób na serwerze.

Drogi reście, proszę zedytuj mi login usera o ID równym 1 na Pablo

W rzeczywistości takie zapytanie w curl mogłoby wyglądać tak:

curl --request PUT --data '{\"login\": \"Pablo\"}' http://app.com/users/1

DELETE

Ostatnia metoda (tak samo jak pierwsza) nie powinna stanowić dla Ciebie żadnego problemu. Metoda DELETE tak jak sama nazwa wskazuje jest przeznaczona do usuwania zasobów na serwerze.

Drogi serwisie, proszę usuń użytkownika o ID równym 1

W curl zapisalibyśmy to tak:

curl --request DELETE http://app.com/users/1

W powyższych komendach curl używałem formatu JSON jako payload zapytania i warto zaznaczyć, że rest nie wymaga określonego formatu danych, choć zazwyczaj stosowany jest znany, prosty JSON.

Warsztat

Skoro masz już niemałe pojęcie czym jest rest i jak go można wykorzystać to nadszedł czas na otworzenie ulubionego IDE, w nim projektu Spring Bootowego i stworzenie pierwszego restowego kontrolera.

Na warsztacie będziemy pracować na tym projekcie, więc rozpocznij od sklonowania tego repozytorium i otworzenia projektu z katalogu template.

Pierwszy kontroler

Od razu powiem, że tworzenie kontrolera jest dziecinnie proste, tak naprawdę można powiedzieć, że wystarczy stworzyć klasę, napisać jedną adnotację…

i kontroler mamy gotowy. Ja do tego schematu dodam jeszcze jeden krok – utworzę najpierw paczkę rest, w którym będę trzymał wszystkie swoje kontrolery aplikacji.

W paczce rest tworzę klasę CamelController, a nad definicją dodaję adnotację @RestController.

@RestController
public class CamelController {
}

I tadam, kontroler gotowy – nie było to trudne, prawda? Jednak może zanim przejdziemy dalej, wyjaśnimy sobie nową tajemniczą adnotację @RestController. Rozbijmy ją sobie na składowe.

Pod adnotacją @RestController kryją się dwie kolejne:

  • @Controller
  • @ResponseBody

Adnotacja @Controller może zostać rozbita znowu na kolejne dwie:

  • @Component
  • @RequestMapping

Adnotację @Component powinieneś już znać – mówi ona o tym, że klasa CamelController jest już Bean Springowym i znajduje się w kontekście Springa.

Adnotacja @RequestMapping oznacza klasę, że ma ona za zadanie przechwytywać zapytania webowe, które są wysyłane pod podany adres.

Zostaje nam jeszcze adnotacja @ResponseBody, ona zaś oznacza, że w odpowiedzi na zapytanie ma być dodawany zserializowany obiekt. Czyli jeśli będziemy mieli w kontrolerze np. metodę zwracającą obiekt typu Camel to zostanie on automatycznie dodany do odpowiedzi na zapytanie.

Nie ma co się obawiać, jak będziemy dalej pisać kod to wszystko zacznie się wyjaśniać.

Pierwszy endpoint GET

Skoro mamy już zdefiniowany kontroler to czas zdefiniować w nim konkretne end-pointy – czyli punkty w naszej aplikacji, do których można się dostać z zewnątrz wykonując odpowiednie zapytanie HTTP.

Na początek zdefiniujemy sobie GETa, który będzie zwracał zwykłego Stringa – tworzymy więc najprostszą metodę:

@RestController
public class CamelController {
    public String getControllerInfo() {
        return "camel-controller";
    }

Jednak to jeszcze nie zadziała, musimy nad metodą dodać kolejną adnotację, którą zdefiniujemy pod jakim adresem i przy użyciu jakiej metody HTTP możemy odpytać endpoint getControllerInfo.

@GetMapping
public String getControllerInfo() {
  return "camel-controller";
}

I to na tyle, czas uruchomić teraz aplikację klikająć Run na klasie CamelApplication i wejść w przeglądarce pod adres http://localhost:8090.

Na ekranie powinien nam się pojawić napis camel-controller. Jeśli tak się stało to wiedz, że wszystko działa poprawnie! 🙂

Następne GETY!

Stworzymy sobie teraz kolejne end pointy, jednak tym razem będziemy chcieli skorzystać z wcześniej napisanego kodu – CamelFacade.

Zanim przejdziemy dalej wstrzyknijmy sobie do naszego kontrolera właśnie tą klasę:

@RestController
public class CamelController {
    private final CamelFacade camelFacade;

    public CamelController(CamelFacade camelFacade) {
        this.camelFacade = camelFacade;
    }
...
}

Zauważ, że nie musisz musisz używać adnotacji @Autowired w przypadku, gdy korzystasz ze Springa 4.3+ i w klasie znajduje się jeden konstruktor.

Mając już wstrzykniętą zależność stwórzmy nowy kontroler, który będzie zwracał listę wielbłądów:

    @GetMapping(value = "/camel")
    public List<Camel> getCamels() {
        return camelFacade.getCamels();
    }

Tym razem w adnotacji @GetMapping podaję wartość argumentu value – definiuje ona pod jakim adresem ma się pojawić tworzony end-point.

Po napisaniu takiego fragmentu kodu uruchommy naszą aplikację od nowa i zajrzyjmy pod adres localhost:8090/camel tak jak zdefiniowaliśmy w adnotacji @GetMapping.

Twoim oczom powinien pojawić się podobny rezultat – możliwe, że u Ciebie nie jest ładnie sformatowany. U mnie robi to wtyczka JSONFormatter do przeglądarki chrome.

Na początku artykułu zacytowałem pewien fragment:

Drogi reście, podaj mi użytkownika o ID równym 1

W tym przypadku zapytanie GET wygląda trochę inaczej, budując URL musimy podać tzw. path parameter, który w naszym przypadku definiuje id użytkownika.

Na szczęście wyciąganie parametrów z URL jest trywialne i ogranicza się do dwóch kroków.

Pierwszy z nich to zdefiniowanie parametru w adresie:

@GetMapping(value = "/camel/{id}")

Drugi krok polega na użyciu adnotacji @PathVariable, dzięki której wartość parametru jest „wrzucana” jako argument metody naszego end-pointu.

@GetMapping(value = "/camel/{id}")
public Camel getCamelById(@PathVariable Long id) {
    return camelFacade.getCamel(id);
}

Po zrestartowaniu aplikacji i wejściu pod adres localhost:8090/camel/0 powinniśmy otrzymać od serwera następującą odpowiedź.

Na pierwszy rzut oka odpowiedź serwera może się dla nas niczym nie różnić od tej poprzedniej, jednak bystre oko powinno dostrzec, że tym razem serwer zwrócił nam obiekt, a nie listę jednoelementową.

Warto też na chwilę zerknąć w implementację metody getCamel w fasadzie

    public Camel getCamel(Long id) {
        return getCamels().stream()
                .filter(c -> c.getId().equals(id))
                .findFirst()
                .orElseThrow(() -> new HttpClientErrorException(HttpStatus.NOT_FOUND));
    }

W przypadku, gdy wielbłąd nie zostanie znaleziony zostaje rzucony HttpClientErrorException, który jako http status code ma podany 404 – Not Found.

Ze względu, że wykorzystałem tutaj Springowy wyjątek, to w przypadku nieznalezienia zasobu zostanie on automatycznie zmapowany na ładną odpowiedź.

POST!

Zejdźmy już z tych nudnych GETów i przejdźmy na coś bardziej ciekawego. W końcu będziemy mieli możliwość w jakiś sposób ingerować w zasoby naszego serwera – czyli będziemy chcieli umożliwić dodawanie nowych wielbłądów.

Tworzenie end pointów z metodą post nie jest również bardzo skomplikowane – tym razem wymieniamy adnotację @GetMapping na @PostMapping.

@PostMapping(value = "/camel")

Następnie deklarujemy metodę:

public Camel createCamel() {
}

Jednak będziemy musieli wziąć skądś tego wielbłąda do stworzenia. Na początku wspomniałem, że używająć metody POST lub PUT mamy możliwość przesyłania tzw. payloadu w dowolnym formacie.

W naszym przypadku będziemy przesyłać w formacie JSON, a mapowanie JSON na obiekt Javowy ogarnie już za nas Spring używając adnotacji @RequestBody.

    @PostMapping(value = "/camel")
    public Camel createCamel(@RequestBody Camel camel) {
        return camelFacade.createCamel(camel);
    }

Adnotacja @RequestBody działa bardzo prosto – bierze body odebranego zapytania i stara się go zmapować na obiekt typu Camel.

W przypadku metody POST nie jesteśmy już w stanie przetestować end pointu bezpośrednio w przeglądarce, jednak możemy to zrobić na kilka sposobów:

  • stworzyć formularz HTML;
  • wysłać zapytanie curl;
  • wykorzystać aplikację POSTMAN (gorąco zachęcam do zainstalowania!).

Aby nie tracić teraz czasu na zaznajamianie się z aplikacją postman, najszybciej będzie otworzyć konsolę. Jeśli korzystasz z Linuxa lub Windowsa 10 powinieneś mieć już wbudowaną komendę curl.

Spróbujmy teraz wysłać zapytanie do naszej aplikacji, aby stworzyć nowego wielbłąda.

curl -H "Content-Type: application/json" -X POST -d {\"name\":\"mruczek\",\"id\":1,\"age\":13,\"gender\":\"MALE\",\"guardians\":[]} http://localhost:8090/camel

Jeśli zapytanie się powiodło, to możemy się teraz udać pod adres localhost:8090/camel i naszym oczom powinny ukazać się już dwa wielbłądy!

Podsumowanie

W tym artykule pokazałem Ci czym jest Rest web serwis, na jakich metodach HTTP jest oparty, czym się różnią od siebie oraz w jaki sposób stworzyć restowy kontroler w Springu.

Kod całej aplikacji znajdziesz w tym projekcie.

Jednak to jeszcze nie koniec, abyś mógł się sprawdzić przygotowałem dla Ciebie krótką pracę domową.

Twoim zadaniem jest dopisanie trzech endpointów, które umożliwią:

  • Pobranie listy wszystkich opiekunów (Guardian)
  • Pobranie opiekuna o konkretnym ID
  • Dodanie nowego opiekuna pod adresem
  • Usunięcie opiekuna o konkretnym ID

Pamiętaj, aby Twoje endpointy nie tylko wykonywały poprawnie logikę, ale również używały odpowiednich metod HTTP zależnie od logiki jaką wykonują.

Rozwiązanie pracy domowej znajdziesz w tym projekcie na branchu: solution.