Vendor lock-in – Historia pewnych map

Największą naukę wynosimy z popełnionych błędów. To właśnie takie sytuacje mogą w zupełności zmienić postrzeganie wykonywanej przez nas pracy, nauczyć nas czegoś o czym wcześniej nie mieliśmy nawet pojęcia lub lekceważyliśmy konsekwencje.

W tym artykule przytoczę jeden z fuckupów do których, dołożyłem swoje pięć groszy.

Na początku chciałbym abyśmy przypomnieli sobie definicję tytułowego vendor lock-in. Tym razem wyjątkowo posłużę się metodą copy-paste z ulubionej encyklopedii:

Vendor lock-in

Uzależnienie od dostawcy (ang. vendor lock-in) – sytuacja, w której klient jest uzależniony od produktów dostawcy do tego stopnia, że nie może zmienić dostawcy bez poniesienia kosztów zmiany. W przypadku oprogramowania termin ten odnosi się do niemożności wymiany komponentu oprogramowania na inny z powodu ich niekompatybilności. Dostawcy oprogramowania tworzą często różne architektury, interfejsy systemów, które uniemożliwiają łatwą migrację. Często taka sytuacja wynika także z błędnego zaprojektowania systemu.

via Wikipedia

Zaznajomiony? A więc chodź…

Opowiem Ci historię

Punktem wyjścia jest odziedziczona aplikacja z wszystkimi swoimi niuansami na poziomie decyzji architektonicznych oraz kodu. Stały rozwój funkcjonalny i szybkie zmiany na poziomie biznesowym nie ułatwiały nam pracy nad poprawą zaciągniętego długu technicznego. Równoważyliśmy to co ważne w biznesie i to co ważne, by ten biznes mógł się dalej rozwijać – czyli usprawnialiśmy elementy na poziomie architektury, kodu zgodnie z wypracowaną listą.

Nie dotarliśmy jednak do jednego z większych usprawnień. Wyprzedziła nas decyzja, która zapadła dużo, dużo wcześniej. Co prawda, była ona na wyciągnięcie ręki, jednak nie zainteresowaliśmy się nią wcześniej, ale o tym później.

W pewien wtorek, nasza aplikacja przestała po prostu działać. Sytuacja była o tyle niekorzystna, że:

  • nic nie działało – czysta, biała strona, wszędzie – serwery testowe, wdrożenie u klienta,
  • brakowało konkretów co się stało,
  • był wtorek, a w środę mieliśmy ustawowy dzień wolny od pracy,
  • za dwa dni czekała nas ważna prezentacja projektu przed potencjalnymi klientami,
  • wstępna analiza wskazywała na gruby fuckup.

Po analizie okazało się, że wycofano wsparcie dla starej wersji biblioteki, która odpowiadała za obsługę głównego elementu naszej aplikacji – mapy Bing.

Microsoft dał nam spory zapas czasu, ponieważ sytuacja miała mieć miejsce już parę miesięcy wcześniej. Ba, pisał o tym na blogu, wysyłał nawet maile z przypomnieniem. Niestety nikt po naszej stronie tego nie nadzorował…

Musieliśmy wykonać aktualizację na poziomie wersji major, co wiązało się ze zmianami na poziomie API biblioteki – pewne elementy zostały usunięte, a obsługa innych zmieniona. Wszystkie różnice pomiędzy wersjami zostały świetnie opisane na Microsoft TechNet.

Analiza naszego problemu

Nasza aplikacja niekonsekwentnie wykorzystywała dwa sposoby obsługi mapy, poprzez:

  • bibliotekę angular-bing-maps,
  • bezpośrednie wykorzystanie API biblioteki natywnej.

Już tłumaczę…

Gotowa biblioteka dla frameworka Angular gwarantowała nam nieco abstrakcji nad bezpośrednimi odwołaniem się do biblioteki natywnej, w zamian mocno ograniczając wydajność. Problemy z wydajnością obchodzone były przez bezpośrednie wywołania biblioteki natywnej – więc mieliśmy małe pomieszanie z poplątaniem. Finalnie w większej części aplikacji bezpośrednio wykorzystywana była biblioteka natywna.

I to dosłownie bezpośrednio. Wyobraź sobie, że w całym kodzie aplikacji masz do wprowadzenia zmiany ponieważ zmianie uległo API biblioteki, którą wykorzystujesz w sposób bezpośredni. Gdyby tylko zamknąć przypadki użycia w module (nawet w jednej klasie 😹) mielibyśmy dużo łatwiejsze zadanie.

Biblioteka nadająca abstrakcję wybrana została w sposób nieprzemyślany. W decision log nikt nawet nie wspomniał o powodach zaciągnięcia do projektu słabo rozwijanej biblioteki zewnętrznej. W momencie wyłączenia starej wersji biblioteki Bing, autorzy angular-bing-maps nie byli w stanie przygotować swojego rozwiązania do nowej wersji – ba, zgłoszony issue do tej pory jest nierozwiązany 😀

Vendor lock-in - Zawiodłem was...

Pomimo, że działaliśmy na odziedziczonej aplikacji i nie wszystko było jeszcze „ogarnięte” to zawiodłem (bo pełniłem rolę Lead Developera) w kilku przypadkach, przyczyniając się do awarii ze względu na:

  • brak podniesienia priorytetu prac dotyczących wprowadzenia abstrakcji nad biblioteką map Bing,
  • brak aktualizacji wykorzystywanych bibliotek,
  • brak śledzenia rozwoju usługi od której uzależniliśmy się w 100%,
  • brak weryfikacji sensu oraz jakości wykorzystanych bibliotek w projekcie.

Obejmując „dowodzenie” niewystarczająco zwróciłem na to uwagę. Niestety, ale zaniedbania wymienione powyżej, doprowadziły do całkowitego zatrzymania aplikacji frontendowej. Mieliśmy spory problem, ubity interfejs, ważna prezentacja lada dzień, więc trzeba było reagować…

Reakcja na problem

Reakcja na problem została podzielona na dwie fazy:

  • reakcja doraźna, która miała na celu jak najszybsze postawienie aplikacji na nogi,
  • reakcja długofalowa, której celem było wprowadzenie abstrakcji map oraz pozbycie się biblioteki angular-bing-maps.

Reakcja doraźna

To tak naprawdę łaty w kodzie, bezpośrednie zmiany i zmiana sposobu ładowania map w aplikacji. Musieliśmy np. zastosować „nieskończoną” pętlę weryfikującą czy zainicjalizowała się nowa biblioteka natywna do obsługi map Bing – ponieważ zmienił się sposób ładowania biblioteki który kłócił się z koncepcją w naszej aplikacji…

Szyliśmy kod tak aby po prostu działał – pod presją czasu. To było wystarczające.

Wszystko zostało wprowadzone w dzień wolny od pracy, po godzinach (sic!), po to by prezentacja projektu, która miała miejsce dzień później mogła się faktycznie odbyć.

Brak testów automatycznych (znikoma ilość – śmiało mogę stwierdzić, że ich nie było) i zmiany w całym kodzie wymagały długich oraz dokładnych testów manualnych w całym zakresie funkcjonalnym naszej aplikacji.

Reakcja długofalowa

Zaraz po usunięciu awarii rozpoczęliśmy planowanie, które miało na celu eliminację podobnego błędu w przyszłości. Prace te dotyczyły następujących elementów:

  • pozbycie się biblioteki angular-bing-maps,
  • wprowadzenie abstrakcji na przypadki użycia map w aplikacji jako modułu,
  • weryfikacja oraz zdecydowanie się na Leaflet co dało nam możliwość bezproblemowego przełączania się pomiędzy różnymi dostawcami map.

Od tego momentu do planowania usprawnień oraz eliminacji długu technicznego dodaliśmy brakujący element – ryzyko. Realizując także, analizę ryzyka w całym projekcie w ujęciu technicznym.

Nauka

Każda porażka to okazja do wyciągnięcia wniosków. Niezależnie czy rozmach błędu był podobnej skali jak nasz czy też nie.

Vendor lock-in - Nauka

W przypadku tej sytuacji zanotowałem trzy wysokopoziomowe aspekty nad którymi warto pracować oraz zwracać uwagę podczas prowadzenia projektu IT.

Analiza ryzyka

Czyli identyfikacja elementów, które zagrażają prawidłowemu funkcjonowaniu naszego projektu. Głównie po to aby przygotować dla najbardziej kluczowych rzeczy plan reagowania. Do prześwietlenia są wszelkie usługi zewnętrzne (usługi AWS/Azure, wysyłka maili, systemy raportujące, analityka itd.), repozytoria (npm, Docker Hub, itp.), wykorzystywane biblioteki – ogólnie wszystko to, co podczas niedostępności lub zmiany negatywnie wpłynie na działanie naszej aplikacji.

Do każdego elementu możesz zadać pomocnicze pytania:

  • Co jeśli usługa przestanie działać – tymczasowo lub na zawsze?
  • Jaki wpływ na aplikację będzie miało wyłączenie usługi?
  • Jakie jest prawdopodobieństwo? Warto zrozumieć czym jest SLA i jaki poziom zapewniają wykorzystywane usługi np. 99,9% to aż 8 godzin 45 minut i 57 sekund przez które usługa może być niedostępna w ciągu roku (prosty kalkulator SLA)
  • W jaki sposób możemy się zabezpieczyć przed awarią?

To świetnie obrazuje jak mocno uzależniliśmy się od czynników zewnętrznych na które nie mamy wpływu. Sama jednak analiza za wiele nam nie da, co najwyżej obnaża nasze słabe strony. Przejdźmy więc dalej.

Reakcja doraźna i długofalowa

Gdy już jednak coś dupnie to przygotujmy sobie plan działania. My zareagowaliśmy na dwóch poziomach:

  • reakcja doraźna – jak najszybsze postawienie aplikacji na nogi, aby użytkownicy odczuwali jak najmniej skutków awarii, to zazwyczaj szybki patch lub jak kto woli trytytka łącząca przerwaną linę,
  • reakcja długofalowa – czyli plan co musi się wydarzyć aby podobna sytuacja nie miała już miejsca.

W sytuacjach takich jak nasza, najważniejsze było aby w przeciągu 24h jakoś to działało. Usprawnienia techniczne zostawiliśmy na później – potencjalnym klientom prezentowano działanie aplikacji, a nie czysty kodzik. Świadomie powiększony dług techniczny spłacaliśmy przez następne kilkanaście dni, wprowadzając rozwiązania wynikające z obranego planu, który zapewniał nam dużo lepszą obsługę podobnej awarii.

Zarządzanie zależnościami

Zależności do bibliotek zewnętrznych dodać do projektu jest bardzo łatwo, w przypadku projektów skupionych wokół języka JavaScript wystarczy npm install biblioteka i wsio. Jednak jak często analizujemy bibliotekę w aspektach:

  • jej utrzymania – reakcji na błędy i ich eliminacji,
  • jej rozwoju – czy ten udostępniony kawałek kodu to nie chwilowa potrzeba autora biblioteki,
  • jej społeczności – czy ktoś w ogóle korzysta z tej biblioteki,
  • jej jakości – ilości błędów nierozwiązanych, wydajności, testów i jakości kodu.

Patrząc na projekty z przeszłości – w ogóle. Co się stanie gdy biblioteka zniknie? Jak zareagujemy? Czy będziemy posiadać dodatkową kopię biblioteki?

Ważne w tym punkcie jest także systematyczne uaktualnianie wykorzystywanych wersji. Update wersji major może wymagać wprowadzenie większej ilości zmian, a następnie pełnych testów regresji – brzmi to jak praca, którą należy sobie odpowiednio zaplanować.

Tak samo jak z rozwagą podchodzimy do implementacji rozwiązań, tak samo z rozwagą powinniśmy podejść i zweryfikować kolejne: npm install nowafajnalibka.

Vendor lock-in nie tylko technicznie

Vendor lock-in to nie tylko problemy techniczne – takie z jakimi musieliśmy się uporać, ale to także problemy dotyczące strefy biznesowej. Kalkulując cennik swojego produktu można brać pod uwagę różne czynniki m.in. wartość dodana produktu, koszty infrastruktury, koszty wykorzystywanych usług zewnętrznych i wiele innych.

Vendor lock-in - Biznes oparty o mapy

Wyobraź sobie, że tworzysz rozwiązanie oparte, tak jak w naszym przykładzie o mapę. Uzależniasz się od Google Maps ze względu na znajomość rozwiązania, przystępny interfejs UI/API, spore możliwości i wysokie limity, które sprawiają, że wykorzystywanie tego rozwiązania nie będzie wiązać się z ponoszeniem kosztu.

Pewnego dnia otrzymujesz maila, który informuje Cię o drastycznie zmieniającym się cenniku wykorzystywanej usługi. Robi Ci się trochę ciepło, bo dobrze wiesz, że do tej pory koszt usługi był zerowy. Kalkulujesz nowy cennik względem tego jak mocno wykorzystujesz usługę i Twój nowy koszt miesięczny „to zaledwie” $5000. 💸

Tego przykładu nie zmyśliłem, to konkretny problem z którym musiał zmierzyć się serwis Gdzie Po Lek. Cała historia została opisana w artykule „Pożegnanie z Google Maps” na blogu Gdzie Po Lek.

Podsumowanie

Nałożyliśmy kilka złych praktyk na siebie powodując wykolejenie projektu.

Mocne uzależnienie od dostawcy może przynosić oszczędność w postaci czasu i/lub finansów. Jednak stwierdzenie, że nigdy nie wprowadzi komplikacji to świat idealny. U nas wystarczyło, że dostawca zaktualizował swoją bibliotekę do nowszej wersji, a działanie poprzedniej zablokował.

Brak zainteresowania ze strony programistów tym jak rozwija się API/SDK usługi to tylko dodatkowy gwóźdź do trumny.

Zarządzaj zależnościami, a nie zaciągaj zależności.

Zarządzaj zależnościami bez znaczenia czy to biblioteka programistyczna czy zewnętrznie wykorzystywana usługa. Śledź i znaj plany rozwoju usług, które wykorzystujesz, reaguj na zmiany, które mogą negatywnie wpłynąć na Twoją aplikację.

Zdrowa abstrakcja nad zewnętrzną biblioteką może zaoszczędzić Ci modyfikacji w całym kodzie aplikacji. Mądre wyodrębnienie modułu do obsługi mapy, zdecydowanie skróciłoby czas potrzebny na postawienie aplikacji na nogi bez nadmiernego zaciągania długu technicznego, którego spłacenie trwało wielokrotnie dłużej (kosztowało wielokrotnie więcej), niżeli wprowadzenie przemyślanego rozwiązania na samym początku.

Zdjęcia: