Przejdź do treści
DedicatedPHP Kontakt

Rozwijanie współdzielonych kontraktów PHP bez blokowania wdrożeń

Przewodnik po stosowaniu wstecznej kompatybilności w PHP przy zmianie współdzielonych komponentów, migracji konsumentów i kontrolowanym wycofywaniu API.

Schemat współdzielonych kontraktów PHP z adapterami, konsumentami i etapami migracji

Pozornie niewielka zmiana we współdzielonej bibliotece PHP może zatrzymać niezależne wdrożenia. Zmiana nazwy parametru, wartości domyślnej lub zastąpienie wyjątku może zepsuć konsumenta, który nie jest dziś wdrażany, znajduje się w innym repozytorium albo wywołuje komponent pośrednio. Błąd może wystąpić w czasie wykonywania, w zadaniu asynchronicznym lub podczas deserializacji danych wygenerowanych przed zmianą.

Wsteczna kompatybilność w PHP nie polega na zachowywaniu każdego historycznego interfejsu. Jest to dyscyplina pozwalająca producentom i konsumentom rozwijać się w różnym tempie, z jasno określonym oknem migracji i weryfikowalnym wycofaniem. Celem jest uniknięcie zarówno wymuszonych skoordynowanych wdrożeń, jak i trwałego gromadzenia przestarzałych API.

Określenie, co stanowi część wewnętrznego kontraktu

Określenie, co stanowi część wewnętrznego kontraktu — guía visual de DedicatedPHP

Wewnętrzny kontrakt to każde zachowanie, od którego zależy inny moduł, nawet jeśli nie jest opublikowane jako zewnętrzne API. Zależności Composer i interfejsy PHP są widoczną częścią, ale nie wyczerpują zakresu. Przed zmianą współdzielonego kodu sprawdź co najmniej następujące elementy:

  • Publiczne sygnatury: nazwy metod, parametry, kolejność, typy, dopuszczalność wartości null, wartości domyślne i typ zwracany.
  • Semantyka: znaczenie każdego argumentu, wymagane pola oraz oczekiwany wynik w określonym warunku.
  • Błędy: rzucane wyjątki, kody błędów, komunikaty przetwarzane przez klientów oraz wyniki null lub puste.
  • Dane: klucze tablic, struktury JSON, komunikaty kolejek, zdarzenia domenowe, serializowane pliki i przechowywane dane.
  • Efekty uboczne: wysyłanie zdarzeń, zapis do bazy danych, unieważnianie cache, wywołania HTTP i kolejność wykonywania.
  • Zachowanie operacyjne: ponowienia prób, idempotencja, limity czasu i obsługa przejściowych awarii.

Na przykład dodanie pola do odpowiedzi JSON jest zwykle addytywne, ale przestaje takie być, jeśli konsument waliduje zamkniętą listę właściwości. Podobnie bardziej szczegółowy wyjątek może być technicznie poprawny, lecz niekompatybilny, jeśli konsument przechwytuje poprzedni wyjątek, aby uruchomić mechanizm odzyskiwania.

Klasyfikacja zmiany przed napisaniem implementacji

Klasyfikacja zapobiega temu, by decyzja projektowa stała się incydentem produkcyjnym. Warto udokumentować ją w propozycji zmiany wraz ze znanymi konsumentami i strategią wyjścia.

Zmiany addytywne

Wprowadzają nową możliwość bez zmiany istniejącej ścieżki: nową metodę, opcjonalny parametr o neutralnej semantyce, dodatkowe zdarzenie lub nową wersję komunikatu. Są preferowaną opcją, gdy konsumenci są wdrażani oddzielnie. Nowa ścieżka musi współistnieć z poprzednią, a wcześniejsze zachowanie musi być zachowane w sposób możliwy do sprawdzenia.

Zmiany kompatybilne dzięki adaptacji

Pozwalają zachować poprzedni wynik za pomocą warstwy tłumaczącej. Na przykład stary interfejs może delegować do nowej usługi, przekształcając argumenty i wyniki. Adaptacja ma sens, jeśli jest zlokalizowana, ma termin wycofania i nie ukrywa różnicy biznesowej, o której konsument powinien świadomie zdecydować.

Zmiany niekompatybilne lub niepewne

Usunięcie metody, zaostrzenie typu, zmiana znaczenia statusu lub modyfikacja przechowywanego formatu są zwykle niekompatybilne. Za niepewną należy też uznać każdą zmianę bez wiarygodnego spisu konsumentów. W obu przypadkach nie wystarczy opublikować nowej wersji pakietu: potrzebne są przejście, zaplanowana migracja albo oddzielna wersja kontraktu.

Budowa weryfikowalnego spisu konsumentów

Nie opieraj decyzji wyłącznie na wyszukiwaniu tekstu. Komponent może docierać do innego za pośrednictwem kontenera zależności, konfiguracji, refleksji, zdarzeń, kolejek lub integracji HTTP. Spis powinien łączyć dowody statyczne i reprezentatywne wykonanie.

  1. Sprawdź zależności zadeklarowane w Composer, ograniczenia wersji oraz repozytoria instalujące pakiet.
  2. Wyszukaj bezpośrednie użycia klas, interfejsów, metod, zdarzeń, kluczy konfiguracji i formatów komunikatów.
  3. Przeanalizuj fabryki, definicje kontenera, listenery, komendy, cron, workery i adaptery infrastruktury.
  4. Zidentyfikuj ścieżki krytyczne: płatności, uwierzytelnianie, zamówienia, synchronizację, powiadomienia i procesy odzyskiwania.
  5. Zarejestruj dla każdego konsumenta właściciela, używaną wersję, ścieżkę migracji oraz dowód ukończenia zmiany.

Publikacja biblioteki i wdrożenie aplikacji to odrębne działania. Opublikowanie kompatybilnej wersji pozwala każdemu konsumentowi aktualizować się, gdy jest gotowy; jednoczesne wdrożenie wszystkich konsumentów zmienia zwykłą ewolucję w kruchą zależność organizacyjną.

Stosowanie ewolucji addytywnej i adapterów na właściwej granicy

Gdy nowe wymaganie zmienia model, najpierw wprowadź nową możliwość i tymczasowo zachowaj poprzednią. Starszy interfejs może delegować do nowej implementacji, o ile konwersja jest jednoznaczna. Dzięki temu konsumenci mogą migrować bez konieczności koordynowania jednego okna.

interface LegacyPriceCalculator
{
    public function calculate(int $amount): int;
}

final class LegacyPriceCalculatorAdapter implements LegacyPriceCalculator
{
    public function __construct(private PriceCalculator $calculator) {}

    public function calculate(int $amount): int
    {
        return $this->calculator->calculate(new Money($amount, 'EUR'))->amount();
    }
}

Adapter zwykle należy na granicy między kontraktami, a nie w rdzeniu domeny. Domena powinna wyrażać aktualny model; tłumaczenie starych argumentów, wartości sentinelowych lub historycznych formatów powinno pozostać w dedykowanej warstwie. Jeśli domena zachowuje warunki dla każdej generacji klientów, historyczna złożoność rozprzestrzenia się na każdą przyszłą modyfikację.

Nie wymuszaj adaptera, gdy występuje utrata informacji lub nowa decyzja biznesowa. Jeśli stary kontrakt nie zawiera danych niezbędnych dla nowego zachowania, utrzymuj oba kontrakty w okresie przejściowym albo wyraźnie zażądaj od konsumenta dodatkowych informacji.

Przekształcenie deprecjacji w zarządzane wycofanie

API oznaczone jako przestarzałe bez alternatywy, terminu i osoby odpowiedzialnej nie jest deprecjacją: to dług bez monitorowania. Użyteczne wycofanie powinno obejmować sygnał w kodzie, instrukcje migracji, warunek usunięcia oraz, jeśli to możliwe, obserwację użycia.

  • Oznacz starszą metodę lub klasę jasną dokumentacją i, jeśli ma to zastosowanie, emituj kontrolowane ostrzeżenie za pomocą trigger_error(..., E_USER_DEPRECATED).
  • Wskaż dokładną alternatywę, w tym różnice w semantyce, błędach i wartościach domyślnych.
  • Zdefiniuj weryfikowalny warunek wyjścia: wszystkie zinwentaryzowane repozytoria zmigrowane, brak zaobserwowanych wywołań lub koniec wsparcia dla określonej wersji.
  • Przypisz osobę odpowiedzialną, która sprawdzi postęp i usunie warstwę po spełnieniu warunku.

Unikaj emitowania niekontrolowanych ostrzeżeń na ścieżkach o dużym wolumenie bez strategii agregacji: szum może ukryć istotne sygnały i podnieść koszt operacyjny. Obserwowalność powinna odpowiadać na konkretne pytanie: którzy konsumenci nadal używają poprzedniego kontraktu i na której ścieżce.

Testowanie przejścia i realizacja sekwencji dostarczania

Testy jednostkowe komponentu same w sobie nie dowodzą, że konsumenci nadal działają. Dodaj testy kontraktowe dla danych wejściowych, wyjściowych i błędów potrzebnych każdemu konsumentowi. Zachowaj przypadki regresyjne dla starego interfejsu, dopóki jest wspierany, oraz jawnie testuj brakujące wartości, wcześniejsze serializowane ładunki i oczekiwane wyjątki.

Bezpieczna sekwencja zwykle przebiega w następującej kolejności:

  1. Opublikuj nowy kontrakt lub implementację addytywną, zachowując poprzednią ścieżkę.
  2. Aktualizuj i wdrażaj konsumentów niezależnie, stosując testy integracyjne tam, gdzie uzasadnia to ryzyko.
  3. Obserwuj błędy, ostrzeżenia o deprecjacji i użycie starszego interfejsu.
  4. Potwierdź spis migracji i rozwiąż wykrytych pośrednich konsumentów.
  5. Usuń adapter lub stary kontrakt w osobnym wdrożeniu, z testami potwierdzającymi ich brak.

Lista kontrolna do zatwierdzenia zmiany

Lista kontrolna do zatwierdzenia zmiany — guía visual de DedicatedPHP
  • Czy objęty zmianą kontrakt jest zdefiniowany szerzej niż sygnatura PHP?
  • Czy zmiana została sklasyfikowana jako addytywna, adaptowalna, niekompatybilna lub niepewna?
  • Czy istnieje spis konsumentów, uwzględniający zdarzenia, dane i ścieżki pośrednie?
  • Czy rozwiązanie pozwala uniknąć wymogu jednoczesnych wdrożeń?
  • Czy adapter, jeśli istnieje, znajduje się poza domeną i ma zaplanowane wycofanie?
  • Czy przetestowano wcześniejsze zachowanie, nową możliwość i oczekiwane błędy?
  • Czy deprecjacja wskazuje alternatywę, warunek wycofania i osobę odpowiedzialną?
  • Czy istnieje sygnał pozwalający wykryć ukryte zależności przed usunięciem API?

Właściwą decyzją nie jest ani utrzymywanie kompatybilności w nieskończoność, ani narzucanie pełnej koordynacji. Należy zaprojektować przejście z granicami: zachować to, co konieczne, migrować na podstawie dowodów i usunąć historyczną kompatybilność, gdy przestaje zapewniać bezpieczeństwo.

Chcesz zastosować te pomysły w swoim projekcie?Omówmy Twoją platformę PHP.
Zobacz powiązaną usługę