Rola
Zespół backendowy
Stack
WordPress, PHP, REST API, AWS
Kluczowe techniki
MVC, Object Cache, DI, SOLID
Typ projektu
API dla aplikacji mobilnej

Kontekst projektu

Nasz zespół dołączył do projektu, w którym aplikacja mobilna - tworzona przez zewnętrzną firmę - miała wyświetlać treści zarządzane w WordPressie. Zadanie wydawało się proste: zarejestrować własne endpointy REST API za pomocą register_rest_route() i zwrócić odpowiednie dane w formacie JSON.

Okazało się jednak, że projekt jest znacznie bardziej złożony niż zakładaliśmy. Aplikacja mobilna była już w zaawansowanej fazie developmentu, a schemat API - definiujący dziesiątki komponentów i ich zagnieżdżone zależności - został narzucony z góry. Każdy endpoint musiał zwracać rozbudowane, modularne struktury danych, gdzie np. komponent Page zawierał podkomponenty typu Image, Text, AudioPlayer, a te z kolei mogły mieć kolejne dzieci.

Schemat architektury API - komponenty i ich zależności

Do tego treści pochodziły z kilku niezależnych instancji WordPressa, a uwierzytelnianie użytkowników było obsługiwane przez AWS Cognito. System musiał być nie tylko funkcjonalny, ale też wydajny i łatwy w rozbudowie.

Wyzwania

Problem 1 - Kilka instancji WordPress

API musiało obsługiwać dane z wielu WordPressów jednocześnie. ID postów mogły się powtarzać między instancjami, a sam endpoint API był dostępny pod wieloma adresami. Rozwiązaniem okazało się wykorzystanie AWS AppSync jako bramy, która kierowała requesty do odpowiednich instancji i ujednolicała dane za pomocą unikalnych identyfikatorów (GUID).

Problem 2 - Bałagan w kodzie

Pierwsze podejście było intuicyjne: każdy komponent API dostał własną klasę PHP. Szybko okazało się, że dużo logiki się powtarza, klasy rozrastają się o kolejne instrukcje warunkowe, a każda kolejna zmiana pogłębiała bałagan.

Problem 3 - Wydajność

Dopiero gdy prawie całe API było gotowe i mieliśmy testową aplikację, okazało się, że generuje ona bardzo dużo requestów. Bez warstwy cachowania API po prostu działało za wolno.

Rozwiązanie: Architektura MVC

W mniejszych projektach wordpressowych wzorce projektowe rzadko są potrzebne. Tutaj było inaczej. Każda zmiana powiększała dług techniczny. Po researchu uznaliśmy, że wzorzec MVC (Model-View-Controller) idealnie pasuje do tego przypadku.

Controller
Przetwarza request, wybiera Model i View
Model
Logika biznesowa, pobieranie danych
View
Formatuje odpowiedź JSON

Kluczową zaletą było odseparowanie logiki od widoku. Jeden View (np. JSON dla komponentu Image) mógł być współdzielony przez wiele Modeli. Gdy w systemie pojawiał się nowy post type, wystarczyło napisać nowy Model i skonfigurować Controller, bez kolejnych if/else w logice.

PHP - Przykład struktury MVC
class PageView {
    public function __construct( PageModel $model ) {}
}

class PageModel {
    public function __construct( WP_Post $post ) {}
}

class ControllerPage {
    public function getComponent( WP_Request $request ) {
        // ...
        return new PageView( new PageModel( $post ) );
    }
}

Optymalizacja: Object Cache API

Dzięki czystej architekturze MVC łatwo było dodać warstwę cachowania. Wykorzystaliśmy wordpressowy Object Cache API, funkcje wp_cache_get() i wp_cache_set(). O tym, czy dany komponent powinien korzystać z cache'a, decydował Model. Dzięki temu nie musieliśmy cachować wszystkiego na ślepo, tylko te elementy, które rzeczywiście były kosztowne do wygenerowania.

Dobre praktyki, które zrobiły różnicę

Dependency Injection

Wszystkie zależności klas dostarczane były z zewnątrz, jako argumenty konstruktora. Dzięki temu, gdy wymagania się zmieniały, wystarczyło przekazać inny obiekt bez modyfikowania samej klasy. To samo podejście umożliwiło łatwe mockowanie zależności w testach.

PHP - Dependency Injection
// Bez DI - klasa tworzy zależność sama
class MyClass {
    public function run() {
        $dependency = new Dependency();
        $dependency->do_stuff();
    }
}

// Z DI - zależność przekazana z zewnątrz
class MyClass {
    public function __construct( Dependency $dependency ) {
        $this->dependency = $dependency;
    }

    public function run() {
        $this->dependency->do_stuff();
    }
}

Zasady SOLID

Przy refaktoryzacji zaczęliśmy świadomie stosować zasady SOLID. Single Responsibility Principle pomogło rozbić rozrośnięte klasy na mniejsze, wyspecjalizowane jednostki. Open/Closed Principle pozwoliło dodawać nowe post type'y bez modyfikowania istniejącego kodu. Dependency Inversion Principle naturalnie wynikło z zastosowania Dependency Injection i interfejsów.


Rezultaty

Skalowalna architektura
Nowy post type = nowy Model, bez zmian w istniejącym kodzie
Lepsza wydajność
Object Cache wyeliminował powtarzalne, kosztowne zapytania
Testowalność
DI umożliwiło łatwe mockowanie i unit testy
Łatwość utrzymania
Czytelny, modularny kod zamiast narastającego bałaganu

Wnioski

Ten projekt pokazał nam, że architektura ma znaczenie, nawet w WordPressie. Wzorce projektowe, zasady SOLID i techniki takie jak Dependency Injection nie są zarezerwowane dla frameworków enterprise. Wystarczy, że projekt zaczyna żyć własnym życiem - rosną wymagania, pojawiają się zmiany, dochodzą nowi developerzy - i nagle okazuje się, że solidne fundamenty to nie luksus, a konieczność.

Dzięki zastosowaniu tych metod mogliśmy dostarczyć API, które było skalowalne, wydajne i przyjemne w utrzymaniu, mimo że pod spodem wciąż działał WordPress.