Kompatybilność wsteczna – co to oznacza i jak o nią dbać?

Kompatybilność wsteczna – co to oznacza i jak o nią dbać?

Czym jest kompatybilność wsteczna?

Kompatybilność wsteczna – najłatwiej wytłumaczyć to, a nawet zobaczyć, na codziennym przykładzie. Pewnie większość z Was kojarzy, że w telefonach jeszcze niedawno standardem były ładowarki ze złączem USB Typu B, jednak od pewnego czasu standardem są już złącza typu C.

Kompatybilność wsteczna złącz USB, źródło: Wikipedia

Jak możecie zauważyć na diagramie – oba te złącza są kompletnie różne. Czyli po prostu wtyczka pasująca do gniazda pierwszego typu, nie będzie pasowała do gniazda drugiego złącza. Wtedy możemy powiedzieć, że te dwa interfejsy (wtyczki) nie są z sobą kompatybilne

Mimo tworzenia nowych telefonów, producenci przez długi czas nie decydowali się na wprowadzenie nowego standardu (USB-C) właśnie ze względu na kompatybilność wsteczną. Po wprowadzeniu nowego modelu z USB-C wszystkie ładowarki i akcesoria dobrze współpracujące z poprzednim standardem i poprzednią wersją telefonu stałyby się bezużyteczne (przynajmniej bez dodatkowych adapterów). Istniała zatem realna obawa, że w związku z brakiem kompatybilności wstecznej klienci niechętnie kupowaliby nowszy model. 

Jaka jest główna definicja kompatybilności wstecznej?

Określona zmiana jest w pełni kompatybilna pod względem interfejsów z resztą systemu, jeżeli obszar po zmianie jest w stanie bez żadnych problemów działać z pozostałymi komponentami. Inaczej mówiąc dowolny element systemu w starej wersji jest w stanie działać z nowym elementem bez potrzeby żadnych dodatkowych zmian.

Niekompatybilność jest więc odwrotnością tego stanu. Występuje ona wtedy, gdy zmiana w jednym systemie A może spowodować popsucie integracji innego systemu B z systemem A. Może ale nie musi, ponieważ system B nie musi korzystać ze zmienionej ścieżki. 

Najlepiej jednak pokazać to na kilku przykładach.

Przy ich analizie zastosujemy następujący schemat:

  • zidentyfikujemy, kto jest klientem dla usługi, która jest dostarczana za pomocą tworzonego przez nas komponentu, 
  • jak wygląda brak kompatybilności, 
  • jakie mogą być skutki tego braku, 
  • w jaki sposób można wykrywać niekompatybilność, 
  • jak jej przeciwdziałać. 

Prosta aplikacja webowa

Prosta aplikacja webowa
Prosta aplikacja webowa

Możemy zacząć od najbardziej klasycznego przypadku: frontend, API i baza danych. W tym przypadku mamy tylko 3 elementy. Bardzo łatwo jest tu zidentyfikować klientów dla każdej z usług:

  • baza danych -API, 
  • API – aplikacja frontendowa, 
  • aplikacja frontendowa – użytkownik. 

Tak więc starając się zachować kompatybilność wsteczną każdy z wymienionych elementów powinien dbać o zapewnienie ciągłości integracji z systemem dla niego klienckim.

Ten przykład jest akurat wyjątkowy. Większość prostych aplikacji webowych tworzona jest jako jedna całość, wdrażana wspólnie. W związku z tym nie musimy się zbyt mocno przejmować tym, czy poszczególne elementy są ze sobą kompatybilne.

Jednak kompatybilność wsteczna ma tu znaczenie w kilku przypadkach:

  • gdy poszczególne elementy są używane przez więcej niż jednego klienta
  • lub gdy wdrażamy niezależnie każdy z elementów systemu i zależy nam na nieprzerwanym działaniu aplikacji przez cały czas wdrożenia (koncepcję zero downtime deployment omawiam w artykule Jak zrobić wdrożenie aplikacji bez przestojów?)
  • nasza aplikacja jest hostowana na wielu serwerach jednocześnie
Aplikacja z Load Balancerem
Aplikacja z Load Balancerem

Wtedy deployment zawsze podmieni aplikacje na jednym serwerze jako pierwszym, więc nowa wersja powinna być kompatybilna wstecz z poprzednią.

Omówmy więc każdy z interfejsów.

Serwis → baza danych

Przykład niekompatybilności

Dobrym przykładem niekompatybilności na poziomie bazy danych jest:

  • skasowanie tabeli, 
  • skasowanie kolumny, 
  • zmiana nazwy tabeli, 
  • ustawienie pola na wymagane (lub dodanie takowego). 

Skutki

W momencie, gdy wprowadzimy w bazie danych tego typu zmiany, pewne ścieżki aplikacji, które wykorzystywały określone tabele przestaną działać. 

Wykrywanie

  • ręczne wykrycie podczas uważnego code review, 
  • automatyzacja – uruchamianie testów integracyjnych na obecnej (lub poprzedniej wersji) aplikacji podłączonej do nowej bazy danych. W takich testach możemy sprawdzać schemat bazy danych lub po prostu uruchamiać funkcjonalne testy na uruchomionej bazie danych.

Przeciwdziałanie

Każda typ niekompatybilnej zmiany musi być rozważony niezależnie. Mimo to, dla każdej z nich możemy znaleźć metodę na zachowanie kompatybilności wstecznej.

  • skasowanie tabeli – pozostawienie tabeli lub widoku o tym samym schemacie odnoszącego się nowego źródła danych, 
  • skasowanie kolumny – pozostawanie kolumny i logiki jej zmiany i ustalenie sposobu rezygnacji z dodatkowej logiki, 
  • zmiana nazwy tabeli – dodanie widoku ze stara nazwą, 
  • ustawienie pola na wymagane (lub dodanie takowego) – pozostawienie niewymagalnego pola i uzupełnianie go w logice aplikacji. 

Frontend → Serwis backendowy

Przykład niekompatybilności

Zakładając, że komunikacja odbywa się przez API, przykłady są następujące:

  • skasowanie metody API, 
  • dodanie wymagalnego pola w metodzie. 

Skutki

Niedziałająca aplikacja front-endowa. Przynajmniej dla określonych endpointów w API.

Wykrywanie

  • testy kompatybilności po stronie API, 
  • automatyzacja – uruchamianie testów end-to-end na obecnej (lub poprzedniej wersji) aplikacji front-endowej podłączonej do nowego API, 
  • jeżeli mamy mechanizm generowania klienta do API po stronie frontendu, możemy w prosty sposób przeanalizować zmiany w interfejsach. 

Przeciwdziałanie

  • możemy zachować stare metody, planując strategię wyjścia z nich, 
  • możemy dodać domyślne parametry w wymaganych polach. 

Użytkownik → Front-end

Ten punkt nie jest oczywisty i często jest pomijany. Mimo to uznałem, że zachowywanie kompatybilności interfejsu użytkownika jest równie istotne co w przypadku API.  

Przykład niekompatybilności

  • kompletne przebudowanie interfejsu i opcji w menu. 

Skutki

  • użytkownik nie jest w stanie obsługiwać aplikacji, bo nic nie znajduje się na swoim miejscu.

Wykrywanie

Ze względu na subiektywność w tym zakresie, nie jest łatwo wykryć niekompatybilności wstecznej w interfejsie użytkownika. Trzeba tu się opierać na wrażeniu testerów lub analityków w naszym zespole. 

Przeciwdziałanie

Możemy wprowadzić na pewien czas możliwość użycia dowolnej z obu wersji wizualnej systemu. Dzięki temu dajemy czas użytkownikom na przyzwyczajenie się do nowego układu graficznego.To rozwiazanie jest często stosowane w tak ważnych dla użytkowników systemach, jak bankowe. 

Reużywalne API

Reużywalne API
Reużywalne API

W tym przypadku mamy do czynienia z usługą API używaną przez wiele różnych aplikacji. Jasno tu widać, że klientami jest wiele aplikacji. Nie muszą one być tworzone w ramach naszej firmy, w szczególności, gdy oferujemy naszą usługę firmom zewnętrznym.

Serwis → Serwis

Przykład niekompatybilności

  • zmiana nazwy metody, 
  • skasowanie metody. 

Skutki

  • niedziałająca funkcjonalność w systemie. 

Wykrywanie

  • testy kompatybilności z nową wersją serwisu po stronie aplikacji klienckich. 

Przeciwdziałanie

  • Stworzenie warstwy kompatybilności ze starymi metodami – Compatibility Layer – pozwoli nam zachować kompatybilność w kontrolowany sposób. Najłatwiej to zrobić pozostawiając stare interfejsy i podpinając je odpowiednio pod nową logikę serwisu.
  • Przy komunikacji API możliwe jest również użycie podejścia dynamicznego odkrywania możliwości – [API Discovery] (patrz również [REST Hypermedia]). Dzięki temu nie mamy ustalonego schematu operacji na naszym serwisie pozostawiając swobodę zmian. Jest to jednak dość skomplikowane zagadnienie.

Reużywalna biblioteka

Reużywalna biblioteka
Reużywalna biblioteka

W tym przypadku współdzielonym zasobem nie jest API, tylko biblioteka kodu, która jest używana w wielu aplikacjach. Zasady są tu dość podobne do poprzedniego punktu. Sprawa jest jednak prostsza, ponieważ biblioteki są z zasady wersjonowane i zamiany w nich nie są w stanie popsuć aplikacji bez wiedzy programistów. 

Aplikacja → Biblioteka

Przykład niekompatybilności

  • skasowanie metody/klasy, 
  • zmiana nazwy. 

Skutki

  • utrudniona migracja na nową wersję biblioteki. 

Wykrywanie

Tu sytuacja jest prosta, jeżeli nasza biblioteka ma testowe aplikacje klienckie, to podbicie wersji biblioteki powinno spowodować błąd podczas budowania takiej aplikacji. Tego typu niekompatybilne zmiany widać więc będzie na code review.

Przeciwdziałanie

  • wersjonowanie biblioteki, 
  • pozostawanie starych metod z oznaczeniem ich jako “depracated” i jasną definicją, w której wersji z nich zrezygnujemy. 

Architektura mikroserwisowa

Architektura mikroserwisowa
Architektura mikroserwisowa

Większość typów interfejsów została już omówiona w poprzednich akapitach. Tutaj skupimy się głównie na komunikacji z systemem kolejkowym. W tym przypadku klientem jest “Data processor” – usługa przetwarzająca komunikaty z kolejki.

Worker obsługujący kolejkę → Kolejka

Przykład niekompatybilności

  • zmiana nazwy wiadomości, 
  • zmiana schematu wiadomości. 

Skutki

  • stare typy wiadomości, których nie będzie potrafił obsłużyć nowy worker, nigdy nie zostaną przetworzone i pozostaną na kolejce.

Wykrywanie

  • próba obsługi nowych wiadomości przez starych konsumentów. 

Przeciwdziałanie

  • tworzenie konwerterów wiadomości starego typu na nowe, 
  • pozostawienie obsługi wiadomości starego typu do czasu kolejnego wdrożenia (i wyczyszczenia kolejki). 

Podsumowanie

Mamy świadomość, jak wiele jest wiele rodzajów niekompatybilności. Zależnie od typu interakcji i sposobu komunikacji.
Z punktu widzenia dewelopera zawsze najłatwiej jest nie patrzeć na kompatybilność wsteczna jest zachowana. Jednak myślenie o tym opłaca się w momencie wdrożenia.
Dobrą wiadomością jest, że każdą niekompatybilność można z zniwelować dzięki zainwestowaniu dodatkowego czasu w rozwój aplikacji.

Wróćmy na chwilę do telefonów, od których zaczęliśmy. Jako użytkownicy już pewnie całkiem niedługo wszyscy zapomnimy o USB typu B i w pełni przyzwyczaimy się do wygodniejszej wersji typu C. Podobnie może być z użytkownikami naszej aplikacji (jeśli tylko wprowadzimy zmiany w sposób dla nich komfortowy). Nawet tymi, którzy do ostatniej chwili będą korzystali ze starej wersji serwisu. 

Zatem w podejmowaniu starań o zachowanie kompatybilności zawsze warto zadać sobie pytanie: czy w naszym przypadku potrzebujemy o to walczyć?