chevron-left chevron-right

[PHP] Jak zwiększyć wydajność REST API wbudowanego w WordPress?

Budując aplikację internetową bardzo często dochodzimy do momentu w którym potrzebne jest źródło danych dla naszej aplikacji. Źródła danych mogą być różne. Mogą to być zewnętrzne serwisy, odczyty danych z urządzeń na których działa aplikacja czy też systemy CMS udostępniające dane poprzez REST API.

W swoim projekcie, jako źródło danych wykorzystuję WordPress'a przy użyciu udostępnionego przez ten system REST API. W jednym z poprzednich wpisów opisałem jak można pobrać dane z WordPress'a za pomocą REST API. W tym wpisie skupię się na wydajności tego rozwiązania. Zaznaczę jednak, że nie będę tutaj omawiał kwestii związanych z cache'owaniem.

Problem z REST API w WordPress

WordPress REST API without changes

Posiadając zwykły serwer na którym można postawić swoją stronę (najczęściej są to serwery wirtualne) możemy spotkać się z problemem wydajności REST API. Ten problem będzie dotyczył wszystkich systemów CMS nie posiadających zaawansowanych mechanizmów ułatwiających cache'owanie obiektów JSON zwracanych przez REST. Problem z wydajnością pojawia się wtedy, gdy trzeba wykonać wiele requestów w krótkim czasie. Serwer lubi się wtedy przytykać na wskutek czego czas potrzebny na pobranie informacji o kilkunastu elementach należy liczyć w sekundach. Ja spotkałem się z takim problemem i udało mi się go rozwiązać.

Co to jest REST?

REST jest to wzorzec projektowy wykorzystywany w wielu aplikacjach internetowych. Sama nazwa pochodzi od słów: REpresentational State Transfer. Jedną z podstawowych własności REST'a jest atomizacja danych, tzn. dane są zwracane w strukturze, które odzwierciedlają wiersz w bazie danych. Co to oznacza?

Wyobraźmy sobie, że mamy bazę danych w której się znajdują tabele: posts i categories. Baza danych jest relacyjna więc między tymi dwoma tabelami zachodzi relacja, że każdy post należy do jednej kategorii. W takiej sytuacji nasza tabela posts może przyjąć strukturę: id | name | content | categoryid. W categoryid przechowujemy informację o ID kategorii pochodzącej z tabeli categories. Gdy wykonamy zapytanie REST'owe w następujący sposób: /api/rest/v1/posts/1 (zupełnie przykładowy URL do pobrania posta o ID równym 1), to powinniśmy otrzymać obiekt JSON w następującej postaci:

1
2
3
4
5
6
{
    "id": "1",
    "name": "Nasz post",
    "content": "Test",
    "categoryid": "1" 
}

Dostaliśmy jedną zwrotkę z bazy danych. Gdybyśmy chcieli pobrać informacje odnośnie kategorii do której przynależy wybrany post, to musimy wykonać dodatkowe zapytanie korzystając z (przykładowego) URL: /api/rest/v1/categories/1. W ten sposób zachowuje mamy pobrany komplet informacji o wybranym przez na poście.

Kolejną rzeczą którą należy zapamiętać jest możliwość korzystania z opcji HTTP takich jak:

  • GET - pobieranie danych,
  • POST - zapisywanie danych,
  • PUT - aktualizowanie danych,
  • DELETE - kasowanie danych.

Oprócz opcji wymienionych powyżej można się spotkać z opcjami: PUBLISH, TRACE, MERGE, HEAD, OPTIONS. Więcej informacji na ten temat można przeczytać tutaj.

Rozwiązanie problemu z wydajnością

Chcąc zredukować liczbę żądań wysyłanych przez aplikację postanowiłem porzucić ideę związaną z atomizacją danych i zacząłem dołączać brakujące dane to obiektów o które się odpytuję za pomocą REST API. W tym celu korzystam z funkcji WordPress o nazwie register_rest_field(). Jest to funkcja, która pozwala nam modyfikować dane zwracane przez REST API.

W moim przypadku wykorzystanie funkcji wygląda następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
function rest_api_create_event_info() {
    /**
     * Rejestruję nowe pole/właśność obiektu JSON.
     * Dane zwrócone przez tą funkcję będą dostępne pod kluczem: event_info
     */
    register_rest_field('sp_event', 'event_info', array(
           'get_callback' => 'get_event_info',
           'schema' => null,
        )
    );
}
 
/**
 * Pobieram dane dotyczące eventu, takie jak:
 * - informacje o rozgrywkach,
 * - informacje o sezonie,
 * - informacje o drużynie gospodarzy,
 * - informacje o drużynie gości,
 * - informacje o zawodnikach obydwu drużyn
 */
function get_event_info( $object ) {
    $postId = $object['id'];
    $competition = wp_get_post_terms($postId, 'sp_league')[0];
    $season = wp_get_post_terms($postId, 'sp_season')[0];
    $homeTeam = get_post($object['teams'][0]);
    $awayTeam = get_post($object['teams'][1]);
 
    $info = array(
        'competition' => $competition,
        'teams' => array($homeTeam, $awayTeam),
        'season' => $season,
        'players' => array()
    );
 
    foreach ($object['players'] as $playerId) {
        if ($playerId > 0) {
            $info['players'][] = get_post($playerId);
        }
    }
 
    return $info;
}
 
/**
 * Przypisuję akcję do procesów WordPress
 */
add_action('rest_api_init', 'rest_api_create_event_info');

Jak widać, za pomocą funkcji register_rest_field() jest w stanie pobrać dodatkowe informacje i udostępnić je pod kluczem event_info w odesłanym obiekcie JSON. To znacząco redukuje liczbę requestów potrzebnych do pobrania wszystkich potrzebnych informacji. Co można zobaczyć na zdjęciu poniżej:

WordPress REST after changes

Podsumowanie

Porównując stan sprzed wprowadzenia zmian ze stanem po wprowadzeniu zmian z łatwością możemy zauważyć, że zdecydowanie zmniejszono ilość potrzebnych requestów w aplikacji, aby móc wyświetlić wymaganą ilość informacji, przyspieszono czas ładowania danych i zmniejszono wagę danych potrzebnych do pobrania. Czas pobrania danych oraz ilość danych do pobrania można zredukować jeszcze bardziej, ale na tym etapie jest to wynik całkowicie satysfakcjonujący dla mnie. Następnym etapem będzie cache'owanie odpowiedzi zapytań, ponieważ część z nich nie będzie się często zmieniać i będą często zwracać te same informacje. Tak więc nie będzie sensu tracić cenny czas na ładowaniu powtarzalnych informacji.

Mam nadzieję, że udało mi się wystarczająco opisać temat i informacje przedstawione w tym wpisie okażą się przydatne w Twoim projekcie. Zapraszam do komentowania i zadawania pytań w komentarzach!

PS. Przy okazji możesz zobaczyć jak wygląda stan aplikacji nad którą pracuję w ramach akcji Daj Się Poznać 2017.

  • Świetny post, jestem ciekawy Twojego zdania na temat stosowania PUT jako PUT i PATCH – między dwiema opcjami są zauważalne różnice, a jednak często ignoruje się tę drugą, dlaczego?

  • To wszystko zależy od przyjętej strategii w implementacji REST. Nie mam jakiegoś konkretnego zdania w tej kwestii. Dopóki przesyłany zestaw informacji jest niewielki, to wydaje mi się, że wybór PUT lub PATCH jest kwestią preferencji. Jednakże, nie wykluczam, że mylę się w tej kwestii.