Gem hell i jak sobie z tym radzic?

Skoro wyszukiwanie hell na tym forum znajduje 0 postow to moze podzielmy sie konkretnymi rozwiazaniami, ktore stosujemy, gdy napotykamy tzw. gem hell :slight_smile:

Definicja w dwoch slowach:

Jako developer, Kiedy dodaje, zmieniam gem, To moze sie okazac, ze zaleznosci nie pozwalaja mi tego zrobic

Na ile to jest wazne? Trzeba zauwazyc, ze z punktu widzenia zleceniodawcy kawalka potrzebnej funkcjonalnosci, trudo jest zrozumiec jakim cudem soft w Ruby nie jest na poziomie deploymentu Open-Closed.
Bo, w tym wypadku nie da sie go rozszerzyc bez zmiany kodu/gemow, ktorych nie chcemy i nie potrzebujemy dotykac.

Dodatkowo semantyczne wersjonowanie, ktoro jakos mogloby uratowac cala sytuacje nie jest kontrolowalne, ani zawsze stosowane :frowning:

Znane rozwiazania:

  • aktualizuj gemy jak najczesciej i ufaj, ze ze soba wspolgraja
    zalety: duza szansa, ze potencjalne konflikty sa male i latwe do rozwiazania. Potencjalnie mniejsza ilosc kodu aplikacji wymaga zmian.
    wady: czestsze updejty = czestsze ryzyka popsucia dzialajacego kodu. Staly punkt “sprintow”

  • nie aktualizuj gemow, do momentu, gdy tego potrzebujesz
    zalety: problem nie istnieje dopoki faktycznie nie potrzebujesz nowych ficzerow gemow
    wady: jak juz sie stanie, to sie dzieje. Dluga obsuwa powoduje, ze API gemow sie zmienia. Stare API, ktorego uzywasz moze juz nie byc wspierane. Bugfixy robione w nowych wersjach gemow. Moze sie okazac, ze trzeba zmieniac duzo kodu aplikacji.

  • dziel aplikacje na mniejsze aplikacje, ktore ze soba rozmawiaja i uzywaja mniejszych podzbiorow gemow. Mniej gemow = mniejszy problem
    zalety: zakres konfliktow/problemow znaczaco w porownaniu do apki “monilitu”.
    wady: dla mniejszych projektow, uzywajacych sporo gemow, moze byc strzelanie do wrobla z armaty.

  • fork gema i samodzielna naprawa zaleznosci
    zalety: w sytuacji, gdy nic innego nie pomaga
    wady: nie zawsze zadziala; dodatkowa zaleznosc do sledzenia w projekcie

Ja bym powiedział raczej „aktualizuj najrzadziej jak się da”, czyli tylko gdy musisz. Każda aktualizacja to potencjalne błędy.

Pewnie wyobrażacie sobie minę początkujących osób (w tym i moją) jak czytają takie sprzeczne porady dotyczące aktualizacji gemów:

  1. rób to jak najczęściej
  2. rób to jak najrzadziej

@Merano
Wyciągnij sam wnioski :wink:
@sharnik Ma rację, ponieważ jeśli coś działa, i działa dobrze, to po co to aktualizować i narażać się na błędy, które mogą przysporzyć Ci kilku nieprzespanych nocy na usuwaniu konfliktów.
Ale z drugiej strony @nthx też ma poniekąd rację. Nowsze wersje gemów zakładają, że będą działać sprawniej, szybciej. Ale czasami brakuje im odpowiednich testów na programistach, ale tych którzy tego gemu używają.

Sam wybierasz ścieżkę, albo oklepane “stare” (nie do końca oczywiście mam to na myśli) gemy, które są sprawdzone i nawet jak posiadają jakiś defekt to w internecie jest masa rozwiązań do tego. Lecz możesz też podążać ścieżką robienia update gemów jak tylko pojawia się kolejna wersja, ale zanim zostanie przetestowana “w praniu” a nie przy użyciu zwyczajnych testów. Wiele może się wydarzyć z Twoim projektem :wink:

Nie do końca rozumiem ten problem. Jeżeli gem A korzysta z gema B, to wiadomo że gem A musi określić z którymi wersjami gema B jest kompatybilny. Gdy w pewnym momencie B wprowadza jakąś nową funkcjonalność i A chce z niej skorzystać to musi podbić swoje wymagania. Nie ma innego wyjścia. Wtedy Ty masz wybór - albo Ci zależy na funkcjonalności, którą wprowadza nowa wersja A i update’ujesz zarówno A i B albo uznajesz, że to za dużo zachodu a korzyści nie są tak duże i zostajesz przy starej wersji A. Jest też szansa, że na przykład nie korzystasz z B bezpośrednio u siebie i wtedy w ogóle problemu nie ma. Możesz też uznać, że twórcy A podbili wymaganie B na wyrost, wtedy ewentualnie możesz go sobie zforkować i ustawić według Ciebie poprawną wersję B.

Odnośnie tego kiedy updateować a kiedy nie.
Jeżeli nowa wersja

  • wprowadza funkcjonalność, z której chcesz skorzystać
  • wprowadza lepszy performance
  • wprowadza jakieś łatki związane z bezpieczeństwem

to updateujesz . Jeżeli nie to chyba nie warto.

A co w przypadku gdy pojawia się gem C, który korzysta z gema B, ale w innej wersji? To jest właśnie gem hell. Dylemat, czy updatować, czy nie, to całkowicie inny problem.

Ja to widzę tak. Jeśli masz dobre pokrycie testami to powinno się aktualizować gemy dosyć często (zakładając, że projekt jest ciągle rozwijany). Przy czym dobre testy pokrywają także funkcjonalności, które odziedziczyłeś z gemów/narzędzie które zostały dodane do projektu.

Przykład. Dodajemy gem devise, który zapewnia nam logowanie, przypominanie hasła, rejestrację itp. Samo dodanie bez pokrycia jest proszeniem się o kłopoty. Oczywiście nie testuję wewnętrznej implementacji devise, tylko albo akceptacyjnie (w tym przypadku), albo integracyjnie. W ogóle dodawanie zależności do aplikacji to temat rzeka.

Załóżmy, że mamy dobre testy. Czy to nam daje pewność, że nic przy aktualizacji coś się nie spierniczy? Oczywiście, że nie ;). Mimo wszystko chyba lepiej jest aktualizować w miarę na bieżąco, poprawiać to co wyłapią testy i poprawić to co wyjdzie dopiero na produkcji, niż nie aktualizować i za parę lat zdać sobie sprawę, że jest się w d**.

1 Like

Fakt to jest problem. Tylko to nie jest problem rubiego,a ogólnie problem bibliotek, które korzystają z innych bibliotek.

Faktycznie sytuacja gdy A i C korzystają z B w różnych wersjach jest dość problematyczna. Najgorszym przypadkiem jest gdy nie ma żadnej pary wersji A i C, które korzystają z tej samej wersji B. Ale wtedy, poza samodzielnym patchowaniem A lub C niewiele możesz zrobić.

Aktualizacja zależnośći to jedna z postaci długu technologicznego, którym należy odpowiedzialnie zarządzać. Decyzja czy robimy częste updaty, czy zostawiany to na później nie różni się niczym od pozostałych problemów związanych z długie technologicznym. Tu nie ma złego rozwiązania, o ile wybrana ścieżka była świadomym wyborem - wszystko zależy od projektu i od stadium, w jakim się znajduje.

Trzeba zauważyć jednak, że znacznie łatwiej uzyskać pomoc maintainerów gemów, jeśli używamy ich aktualnych wersji, oficjalnie oznaczonych jako “wspierane”. Zupełnie nie wyobrażam sobie, by aplikacja, która jest w aktywnym developmencie używała niesupportowanych narzędzi - całkowicie inaczej wygląda to w czasie tzw. “maintenance mode”, czy gdy taka decyzja podjęta jest świadomie (Github dopiero ostatnio przeszedł na railsy 3 - wiązało się to jednak z pracą ich developerów nad utrzymaniem własnego forka railsów 2).

… Wiec jednak sobie wyobrazasz, ze czasem wazniejsze jest dostarczanie funkcjonalnosci, niz uzywanie najnowszych gemow

Mam na codzien doczynienia z softem, ktory zarabia.

Niestety, co jakis czas musze sie pochylic nad Rubym, bundlerem i zatrzymac development, tylko i wylacznie dlatego, ze ficzer Ruby jako platformy do developmentu: “chce skorzystac z gema - nie psujac pozostalych” w Ruby nie dziala. Ot tak, a co tam … dobrze, ze ma inne zalety :wink:

Oczywiście, że sobie wyobrażam - jeśli kogoś stać, żeby mieć cały zespół pracujący wyłącznie nad utrzymaniem starych gemów. A później mieć zespół 4 developerów pracujących przez pół roku nad upgradem (źródło). Ale chyba musimy przyznać, że większość aplikacji z jakimi pracujemy to nie ta skala, nie ten poziom.

1 Like

Każdy kto opiera swój biznes na sofcie musi sobie zdawać sprawę z problemu długo technologicznego. Jeśli ktoś angażuje 100% mocy tylko w realizację potrzeb biznesowym to niestety powiększa sobie ten dług (choć może zupełnie o tym nie wiedzieć).

Dług technologiczny zaciągasz od pierwszej linijki kodu, nawet wcześniej, od momentu gdy wybrałeś framework i np. bazę danych. Jeśli taka osoba na “górze” nie czai tego to może taki przykład do niej trafi. Taxówka służy do realizacji celu biznesowego. Wymaga jednak częstych przeglądów, wymiany tego co się zużywa, a po X latach może i nawet wymiany na nowszy model. Oczywiście możesz oszczędzać na przeglądach i wymianie części, ale trzeba się liczyć z konsekwencjami.

I serio ten problem dotyczy tylko Rubiego? Nowsze wersje biblioteki są często niekompatybilne ze starszymi bo:

  • autor zmienił zdanie co do designu, interfejsu klas
  • API zostało zachowane, ale programista który użył biblioteki oparł się na prywatnym API albo jakimś specyficznym zachowaniu, które nie jest już wspierane (ktoś pamięta jeszcze alias_metchod_chain?)
  • biblioteka jest niby kompatybilna, ale jej nowsze zależności nie są kompatybilne z naszymi

Żaden z tych problemów nie jest specyficzny dla rubiego.

Problem jest powszechny, ale nie nierozwiazywalny.

Cytujac wikipedie i JAR Hell:

The OSGi Alliance specified (starting as JSR 8 in 1998) a modularity
framework that solved JAR hell for current and future VMs in ME, SE,
and EE that is widely adopted. Using metadata in the JAR manifest, JAR
files (called bundles) are wired on a per-package basis. Bundles can
export packages, import packages and keep packages private, providing
the basic constructs of modularity and versioned dependency
management. http://en.wikipedia.org/wiki/Java_Classloader

Niestety zostalo to zapuszczone, ale furtka pozostaje. Java, Scala, … JRuby?

Fajnie napisales o dlugu technologicznym…

Zgadzam się z Radarkiem. Im dłużej udajemy , że problemu nie ma w tym czarniejszej dupie się później znajdziemy. Inna sprawa , że odpalanie “bundle update” gdy najdzie nas ochota to też nie najlepszy pomysł. Warto dbać o to żeby działac na odpowiednich wersjach każdego gema. Bardzo ważna też jest wiedza z czego korzysta dany gem, jakie ma zależności, czemu używamy tej a nie innej wersji tego gema.
Jeżeli razem z gemami będziemy aktualizować tą wiedzę, nie powinniśmy mieć większego problemu z przechodzeniem na nowsze wersje odpowiednich gemów - a przynajmniej będziemy mieć mniejsze ;D

Tak naprawdę większym problemem (przynajmniej w moim przypadku) jest uświadomienie sobie i klientowi jak ważne jest zadbanie o to aby aktualizować wszystkie gemy ( w tym railsy, jeżeli nie railsy szczególnie) , a nie samo wykonanie tego. Co do samego klienta to wydaje mi się , że nie każdy klient jest gotowy o tym wiedzieć - trzeba to chyba robić za jego plecami, aby był zadowolony ;]

Życie to sztuka tradeoffów (przepraszam za truizm). Alternatywą jest brak gema i napisanie tego samemu, albo gemy zamrożone po czwartym commicie ŻEBY CZASEM KOMUŚ NIE ZŁAMAĆ KOMPATYBILNOŚCI.

Zwłaszcza że nikt nie broni ci wskazać bundlerowi że ma korzystać z gema w jednej i konkretnej wersji, która akurat działa z resztą twojego stosu nieaktualizowanego od trzech kwartałów.

2 Likes

Akurat broni. Zmiany - w gemach, z ktorymi mialem doczynienia - nie byly kompatybilne z tym co oczekiwaly trzecie gemy.
Lockowanie wersji bylo raczej stworzone, aby je kontrolowac, a nie “hackowac” :wink:

Anyway… ja wybralem podejscie nr 3 zmojego zestawu: dzielenie monolitu na kilka, mniejszych serwisow.

Problem z gemami jest najbardziej odczuwalny w apce railsowej, ktora ma ich najwiecej: ± 50
Inna ma ich 15 - sinatra
Inna 8
Inna 6
Inna 9
Inna 10
itp… itd…
Wspolnie tworza jeden organizm.

W praktyce, jestem zadowolony z podejscia. Jak pisalem: mniejsza apka, mniejszy problem.