REST oldalszámozás tavasszal

REST felső

Most jelentettem be az újat Tanulj tavaszt tanfolyam, amelynek középpontjában az 5. tavasz és a tavaszi bakancs 2 alapjai állnak:

>> ELLENŐRIZZE A FOLYAMATOT

1. Áttekintés

Ez az oktatóanyag a következőkre fog összpontosítani a pagináció megvalósítása egy REST API-ban, a Spring MVC és a Spring Data felhasználásával.

2. Oldal mint erőforrás vs Oldal mint ábrázolás

Az első kérdés, amikor a RESTful architektúrával összefüggésben tervezzük a lapozást, az, hogy figyelembe vesszük-e a oldal egy tényleges erőforrást vagy csak az erőforrások ábrázolását.

Magát az oldalt erőforrásként kezelve számos probléma merül fel, például az, hogy a hívások között már nem lehet egyedi módon azonosítani az erőforrásokat. Ez azzal a ténnyel párosulva, hogy a perzisztencia rétegben az oldal nem megfelelő entitás, hanem szükség esetén felépített tartó, a választást egyértelművé teszi: az oldal az ábrázolás része.

A következő kérdés a lapozástervezésben a REST összefüggésben hová kell beilleszteni a lapozási információkat:

  • az URI útvonalon: / foo / page / 1
  • az URI lekérdezés: / foo? page = 1

Ezt szem előtt tartva egy oldal nem erőforrás, az oldalinformációk kódolása az URI-ban már nem lehetséges.

A probléma megoldásának szabványos módját fogjuk használni a lapozóinformációk kódolása URI lekérdezésben.

3. A vezérlő

Most a megvalósításért - a tavaszi MVC vezérlő a lapozáshoz egyszerű:

@GetMapping (params = {"page", "size"}) public list findPaginated (@RequestParam ("page") int page, @RequestParam ("size") int size, UriComponentsBuilder uriBuilder, HttpServletResponse response) {Page resultPage = service .findPaginated (oldal, méret); if (page> resultPage.getTotalPages ()) {dobja az új MyResourceNotFoundException () parancsot; } eventPublisher.publishEvent (új PaginatedResultsRetrievedEvent (Foo.class, uriBuilder, válasz, oldal, resultPage.getTotalPages (), méret)); return resultPage.getContent (); }

Ebben a példában a két lekérdezési paramétert injektáljuk, méret és oldal, a Controller módszerben keresztül @RequestParam.

Alternatív megoldásként használhatnánk a Lapozható objektum, amely feltérképezi a oldalt, méret, és fajta paraméterek automatikusan. Ezen felül a PagingAndSortingRepository entitás olyan dobozon kívüli módszereket biztosít, amelyek támogatják a Lapozható paraméterként is.

Injektáljuk a Http Response-t és a UriComponentsBuilder hogy segítsen a Felfedezhetőségben - amelyet egy egyedi eseményen keresztül szétválasztunk. Ha ez nem az API célja, egyszerűen eltávolíthatja az egyéni eseményt.

Végül - vegye figyelembe, hogy ennek a cikknek a középpontjában csak a REST és a webréteg áll - hogy elmélyüljön a lapozás adatelérési részében, olvassa el ezt a cikket a Lapozás a tavaszi adatokról című cikkben.

4. Felfedezhetőség a REST oldalszámozáshoz

A lapozás körében a A HATEOAS kényszer a Pihenésre azt jelenti, hogy az API kliensének lehetővé kell tennie az következő és előző oldalak a navigáció aktuális oldala alapján. Erre a célra, használni fogjuk a Link HTTP fejléc, a „következő“, “prev“, “első”És„utolsó”Linkkapcsolati típusok.

A REST-ben A felfedezhetőség átfogó kérdés, nemcsak meghatározott műveletekre, hanem műveletek típusaira is alkalmazható. Például minden erőforrás létrehozásakor az ügyfélnek fel kell fedeznie az adott erőforrás URI-ját. Mivel ez a követelmény releváns a BÁRMILYEN erőforrás létrehozása szempontjából, külön kezeljük.

Ezeket az aggályokat az események segítségével bontjuk szét, amint azt a REST szolgáltatás felfedezhetőségére összpontosító előző cikkben tárgyaltuk. Lapozás esetén az esemény - PaginatedResultsRetrievEEvent - kilövi a vezérlő rétegben. Ezután egy egyedi hallgatóval megvalósítjuk a felfedezhetőséget ehhez az eseményhez.

Röviden: a hallgató ellenőrzi, hogy a navigáció lehetővé teszi-e a következő, előző, első és utolsó oldalakat. Ha mégis megteszi - megteszi adja hozzá a releváns URI-kat a válaszhoz „Link” HTTP fejlécként.

Menjünk most lépésről lépésre. A UriComponentsBuilder A vezérlőtől továbbított adatok csak az alap URL-t tartalmazzák (a gazdagépet, a portot és a kontextus elérési utat). Ezért hozzá kell adnunk a fennmaradó szakaszokat:

void addLinkHeaderOnPagedResourceRetrieval (UriComponentsBuilder uriBuilder, HttpServletResponse response, Class clazz, int page, int totalPages, int size) {String resourceName = clazz.getSimpleName (). toString (). toLowerCase (); uriBuilder.path ("/ admin /" + erőforrásnév); // ...}

Ezután a StringJoiner összefűzni az egyes linkeket. Használjuk a uriBuilder az URI-k előállításához. Nézzük meg, hogyan haladnánk tovább a következő oldal:

StringJoiner linkHeader = új StringJoiner (","); if (hasNextPage (oldal, totalPages)) {String uriForNextPage = constructNextPageUri (uriBuilder, oldal, méret); linkHeader.add (createLinkHeader (uriForNextPage, "következő")); }

Vessünk egy pillantást a constructNextPageUri módszer:

String constructNextPageUri (UriComponentsBuilder uriBuilder, int page, int size) {return uriBuilder.replaceQueryParam (PAGE, page + 1) .replaceQueryParam ("size", size) .build () .encode () .toUriString (); }

Hasonlóan fogunk eljárni a többi URI esetében is, amelyeket fel akarunk venni.

Végül hozzáadjuk a kimenetet válasz fejlécként:

response.addHeader ("Link", linkHeader.toString ());

Vegye figyelembe, hogy a rövidség kedvéért itt csak egy részleges kódmintát és a teljes kódot vettem fel.

5. Teszt vezetési oldalszámozás

A lapozás fő logikáját és a felfedezhetőséget egyaránt kicsi, koncentrált integrációs tesztek fedik le. Az előző cikkhez hasonlóan mi is a REST-biztosított könyvtárat fogjuk használni a REST szolgáltatás fogyasztására és az eredmények ellenőrzésére.

Ez néhány példa a paginációs integrációs tesztekre; A teljes tesztcsomag megtekintéséhez nézze meg a GitHub projektet (link a cikk végén):

@Test public void whenResourcesAreRetrievedPaged_then200IsReceived () {Response response = RestAssured.get (paths.getFooURL () + "? Page = 0 & size = 2"); assertThat (response.getStatusCode (), van (200)); } @Test public void whenPageOfResourcesAreRetrievedOutOfBounds_then404IsReceived () {String url = getFooURL () + "? Page =" + randomNumeric (5) + "& size = 2"; Válaszválasz = RestAssured.get.get (url); assertThat (response.getStatusCode (), van (404)); } @Test public void givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources () {createResource (); Válaszválasz = RestAssured.get (paths.getFooURL () + "? Page = 0 & size = 2"); assertFalse (response.body (). as (List.class) .isEmpty ()); }

6. Tesztvezetési oldalak felfedezhetősége

Annak tesztelése, hogy a lapozás felfedezhető-e az ügyfél számára, viszonylag egyszerű, bár sok teret kell lefedni.

A tesztek az aktuális oldal helyzetére koncentrálnak a navigációban és az egyes pozíciókból felfedezhető URI-k:

@Test public void whenFirstPageOfResourcesAreRetrieved_thenSecondPageIsNext () {Response response = RestAssured.get (getFooURL () + "? Page = 0 & size = 2"); Karakterlánc uriToNextPage = extractURIByRel (response.getHeader ("Link"), "next"); assertEquals (getFooURL () + "? page = 1 & size = 2", uriToNextPage); } @Test public void whenFirstPageOfResourcesAreRetrieved_thenNoPreviousPage () {Response response = RestAssured.get (getFooURL () + "? Page = 0 & size = 2"); Karakterlánc uriToPrevPage = extractURIByRel (response.getHeader ("Link"), "prev"); assertNull (uriToPrevPage); } @Test public void whenSecondPageOfResourcesAreRetrieved_thenFirstPageIsPrevious () {Response response = RestAssured.get (getFooURL () + "? Page = 1 & size = 2"); Karakterlánc uriToPrevPage = extractURIByRel (response.getHeader ("Link"), "prev"); assertEquals (getFooURL () + "? page = 0 & size = 2", uriToPrevPage); } @Test public void whenLastPageOfResourcesIsRetrieved_thenNoNextPageIsDiscoverable () {Response first = RestAssured.get (getFooURL () + "? Page = 0 & size = 2"); Karakterlánc uriToLastPage = extractURIByRel (first.getHeader ("Link"), "last"); Válaszválasz = RestAssured.get (uriToLastPage); Karakterlánc uriToNextPage = extractURIByRel (response.getHeader ("Link"), "next"); assertNull (uriToNextPage); }

Vegye figyelembe, hogy a (z) teljes alacsony szintű kódja extractURIByRel - felelős az URI-k kibontásáért rel reláció van itt.

7. Minden erőforrás megszerzése

A lapozás és a felfedezhetőség ugyanazon témájában a választást akkor kell megtenni, ha az ügyfélnek megengedett az összes erőforrás lekérése a rendszerben, vagy ha az ügyfélnek lapozva kell kérnie őket.

Ha úgy döntünk, hogy az ügyfél nem tudja lekérni az összes erőforrást egyetlen kéréssel, és az oldalszámozás nem kötelező, de kötelező, akkor az összes válasz kérésére adott válaszra számos lehetőség áll rendelkezésre. Az egyik lehetőség a 404 (Nem található) és használja a Link fejléc, hogy az első oldal felfedezhető legyen:

Link =; rel = ”első”,; rel = ”utolsó”

Egy másik lehetőség az átirányítás visszaadása - 303 (Lásd: Egyéb) - az első oldalra. Konzervatívabb út az, ha egyszerűen visszaadunk az ügyfélnek egy 405-ös számot (Nem megengedett módszer) a GET kéréshez.

8. Pihenő lapozás Hatótávolság HTTP fejlécek

A pagináció megvalósításának viszonylag eltérő módja a HTTP Hatótávolság fejlécekHatótávolság, Tartalom-tartomány, If-Range, Elfogadási tartományok - és HTTP állapotkódok – 206 (Részleges tartalom), 413 (A kérelem entitása túl nagy), 416 (A kért tartomány nem kielégítő).

A megközelítés egyik nézete az, hogy a HTTP tartomány kiterjesztéseket nem lapozásra szánták, és hogy azokat a szervernek, nem pedig az alkalmazásnak kell kezelnie. Ennek ellenére technikailag lehetséges a HTTP tartomány fejléc kiterjesztésein alapuló oldalszámozás, bár közel sem olyan általános, mint az ebben a cikkben tárgyalt megvalósítás.

9. Tavaszi adatok REST oldalszámozás

Ha a Spring Data-ban vissza kell adnunk néhány eredményt a teljes adatsorból, bármelyiket felhasználhatjuk Lapozható repository metódus, mivel mindig visszatér a Oldal. Az eredményeket az oldalszám, az oldalméret és a rendezési irány alapján adjuk vissza.

A Spring Data REST automatikusan felismeri az URL paramétereket, mint például oldal, méret, rendezés stb.

Bármely tároló személyhívó módszereinek használatához ki kell bővítenünk PagingAndSortingRepository:

nyilvános felület A SubjectRepository kiterjeszti a PagingAndSortingRepository {}

Ha hívunk // localhost: 8080 / subject Tavasz automatikusan hozzáadja a oldal, méret, rendezés paraméter javaslatok az API-val:

"_links": {"self": {"href": "// localhost: 8080 / subject {? page, size, sort}", "templated": true}}

Alapértelmezés szerint az oldal mérete 20, de megváltoztathatjuk, ha valami hasonlót hívunk // localhost: 8080 / subject? page = 10.

Ha be akarjuk építeni a lapozást a saját egyéni adattárunk API-jába, akkor át kell adnunk egy kiegészítőt Lapozható paramétert, és ellenőrizze, hogy az API visszaadja-e a Oldal:

@RestResource (path = "nameContains") public page findByNameContaining (@Param ("name") Karakterlánc neve, Lapozható p);

Amikor hozzáadunk egy egyedi API-t a /keresés a végpont hozzáadódik a létrehozott hivatkozásokhoz. Tehát, ha hívunk // localhost: 8080 / subject / search látni fogunk egy paginációra képes végpontot:

"findByNameContaining": {"href": "// localhost: 8080 / subject / search / nameContains {? név, oldal, méret, rendezés}", "sablon": true}

Az összes API, amely megvalósul PagingAndSortingRepository visszaadja a Oldal. Ha vissza kell adnunk az eredmények listáját a Oldal, a getContent () API-ja Oldal biztosítja a Spring Data REST API eredményeként letöltött rekordok listáját.

Az ebben a szakaszban szereplő kód elérhető a spring-data-rest projektben.

10. Átalakítás a Lista ba be Oldal

Tegyük fel, hogy van egy Lapozható objektum bemenetként, de az információkat, amelyeket be kell szereznünk, az a helyett egy lista tartalmazza PagingAndSortingRepository. Ezekben az esetekben szükségünk lehet átalakítani a Lista ba be Oldal.

Képzelje el például, hogy van egy SOAP-szolgáltatás eredménylistája:

Lista lista = getListOfFooFromSoapService ();

Hozzá kell férnünk a listához a Lapozható nekünk küldött tárgy. Tehát definiáljuk a kezdő indexet:

int start = (int) lapozható.getOffset ();

És a végindex:

int vég = (int) ((kezdet + lapozható.getPageSize ())> fooList.size ()? fooList.size (): (start + lapozható.getPageSize ()));

Ha ezt a kettőt a helyén tartjuk, létrehozhatunk egy Oldal a köztük lévő elemek listájának megszerzéséhez:

Oldaloldal = new PageImpl (fooList.subList (kezdet, vég), lapozható, fooList.size ());

Ez az! Most visszatérhetünk oldalt érvényes eredményként.

És vegye figyelembe, hogy ha a rendezéshez is támogatást akarunk adni, akkor meg kell rendezze a listát az allisták előtt azt.

11. Következtetés

Ez a cikk bemutatta, hogyan lehet a Paginationt egy REST API-ban implementálni a Spring használatával, és megvitatta, hogyan kell beállítani és tesztelni a Discoverability-t.

Ha részletesen át szeretné tekinteni a lapozást a perzisztencia szintjén, nézze meg a JPA vagy a Hibernate lapozás oktatóanyagaimat.

Ezeknek a példáknak és kódrészleteknek a megvalósítása megtalálható a GitHub projektben - ez egy Maven-alapú projekt, ezért könnyen importálhatónak és futtathatónak kell lennie.

REST alsó

Most jelentettem be az újat Tanulj tavaszt tanfolyam, amelynek középpontjában az 5. tavasz és a tavaszi bakancs 2 alapjai állnak:

>> ELLENŐRIZZE A FOLYAMATOT

$config[zx-auto] not found$config[zx-overlay] not found