współautor: Sanket Patel
Wprowadzenie
Masz aplikację Rails 5 (lub starszą) korzystającą z Sidekiq i chcesz zaktualizować ją do najnowszej i najlepszej wersji? Jeśli tak, zostań z nami, aby zapoznać się z kilkoma przydatnymi wskazówkami i trikami, które odkryliśmy podczas tej samej sytuacji. Mamy nadzieję, że pomogą Ci zaoszczędzić czas podczas aktualizacji.
W Rewind szeroko wykorzystujemy Sidekiq 5 w naszych aplikacje do tworzenia kopii zapasowych i kopiowania zadania asynchroniczne.
Używamy silnika Rails zamontowanego w aplikacji Rails, która obsługuje Sidekiq. Silnik zawiera wszystkich pracowników i inną logikę biznesową. Wygląda to mniej więcej tak:

Podczas aktualizacji z Rails 5 do 6 odkryliśmy, że nasze gemy i silnik Rails zawierały kod niezgodny z nowym domyślnym autoloaderem o nazwie Zeitwerk, przez co serwery aplikacji nie uruchamiały się. Dowiedzieliśmy się również, że Sidekiq 6 wymaga automatycznego ładowania Zeitwerk i nie będzie działać w trybie klasycznym.
Aktualizacja głównej wersji biblioteki innej firmy budzi pewien niepokój. Dodanie fundamentalnej zmiany w sposobie ładowania i odczytywania klas przez aplikację? Auć.
Zespół zdecydował się obejść ten problem, przywracając klasyczny autoloader i pozostawiając Sidekiq w wersji 5.
Mimo wszystko minął prawie rok od wydania Sidekiq 6 i aktualizacja była konieczna już dawno.
Do tej pory wydano wersję Sidekiq 6.1, która zawierała szereg poprawek bezpieczeństwa, błędów i udoskonaleń, które miały na celu ulepszenie naszej usługi – potrzebne aby dowiedzieć się, jak to będzie wyglądać na naszej platformie.
W klasycznym stylu Rewind wygospodarowaliśmy trochę czasu i opracowaliśmy plan mający na celu ograniczenie promienia rażenia ulepszenia.
Oto 9 wskazówek i trików, które powinieneś znać, zanim zaczniesz samodzielnie przeprowadzać aktualizację:
1) zeitwerk:check jest twoim przyjacielem
Gem zeitwerk jest częścią Rails 6 i zawiera przydatne narzędzie sprawdzające, które skanuje folder projektu i próbuje załadować się w trybie przyspieszonym, tak jak aplikacja uruchamia się podczas uruchamiania. Znaleźliśmy dwa sposoby jego użycia:
W aplikacji Rails:
bundle exec rake zeitwerk:check
W silniku Rails:
bundle exec rake app:zeitwerk:check
2) Użyj Inflectorów, aby poradzić sobie z dziwactwami wielkości liter
Zeitwerk preferuje czcionki camelcase, a jeśli masz klasy składające się z następujących po sobie wielkich liter i nie chcesz zmieniać ich nazw, możesz wykorzystać Zeitwerk::Inflector w inicjatorze Rails:
# config/initializers/zeitwerk.rb
Rails.autoloaders.each do |autoloader|
autoloader.inflector.inflect(
'gdpr_handler' => 'GDPRHandler'
)
end
Bez tego zeitwerk oczekuje, że Twoja klasa będzie nazywać się „GdprHandler”
3) Nie układaj inicjatorów inflektorów w stosy
W naszym silniku Rails utworzyliśmy instancje flektorów, jak pokazano poniżej, ale okazało się, że zostały one nadpisane, gdy aplikacja Rails korzystająca z silnika również utworzyła własne instancje flektorów.
# config/initializers/zeitwerk.rb
Rails.autoloaders.each do |autoloader|
autoloader.inflector = Zeitwerk::Inflector.new
autoloader.inflector.inflect(
'graph_ql_query_service' => 'GraphQLQueryService',
'graph_ql' => 'GraphQL'
)
end
Jeśli masz silnik wykorzystujący tę technikę, możesz obejść tę sytuację, konfigurując inicjator w swojej aplikacji Rails, jak pokazano we wskazówce nr 2, aby dodaje do reflektorów, a nie zastępuje ich.
4) Obawy to słowo zastrzeżone
Mieliśmy Rails Concerns w przestrzeni nazw modułu „Concerns” poniżej katalogu „app” w aplikacji Rails. Zeitwerkowi się to nie spodobało. Mieliśmy więc dwie możliwości: usunąć przestrzeń nazw lub zmienić nazwę przestrzeni nazw modułu.
Zmieniliśmy nazwę przestrzeni nazw pliku z informacjami o problemie z „Concerns::Foo” na „Support::Foo”
Przyjrzyjmy się bliżej temu, co to oznacza, aby uzyskać ogólne zrozumienie:
Weźmy na przykład:
# file at /app/foo/concerns/something.rb
module Foo::Concerns
module Something
Powyższe jest w porządku, tak zrobimy include Foo::Concerns::Something
Ale poniżej nie jest tak:
# file at /app/concerns/something.rb
module Concerns
module Something
Zeitwerk będzie więc narzekał na to: include ::Concerns::Something
Tak więc zasadniczo nie możesz mieć ::Concerns::Something z tego samego powodu, dla którego nie możesz mieć Models::User or Controllers:UserController
Jeśli nie chcesz zmieniać przestrzeni nazw „Concerns” na coś innego, np. „Support”, możesz wykonać poniższe czynności:
# plik w /app/concerns/something.rb moduł Coś # następnie dołącz jak poniżej dołącz Coś
W ten sposób możesz nadal przechowywać „Zagadnienia” w folderze „Zagadnienia” i uniknąć konieczności zmiany struktury swojej aplikacji.
Zasadniczo nie można mieć modułu zaczynającego się od „Obawy”.
5) Możesz swobodnie wykluczyć łaty małp
W naszym silniku mamy poprawki typu monkey, które naruszały polecenie zeitwerk:check. Na przykład, wprowadzamy pewne niestandardowe zmiany w klasie ActiveResource i musimy wstrzyknąć logikę do klasy Base. Mamy plik o nazwie lib/active_resource/base_ext.rb, który definiuje klasę ActiveResource::Base, ale pojawia się błąd, który mówi, że musi on definiować klasę o nazwie ActiveResource::BaseExt.
Rozwiązanie, które tutaj zastosowaliśmy, polegało po prostu na skonfigurowaniu zeitwerka tak, aby ignorował tę konkretną ścieżkę w folderze lib:
moduł ładujący = Zeitwerk::Loader.for_gem moduł ładujący.ignore(“#{__dir__}/active_resource”)
Uwaga: Później konieczne będzie ręczne zażądanie tych plików.
6) Podziel pojedyncze pliki z wieloma klasami do osobnych plików
W naszym silniku mieliśmy plik wyjątków o nazwach w przestrzeni nazw wyglądających następująco:
# lib/foo/exceptions.rb class MissingConfigurationKeys < StandardError def initialize(msg = 'Brakuje jednego lub więcej wymaganych kluczy konfiguracji lub są one nieprawidłowe') super end end class MappingsOutOfDate < StandardError def initialize(msg = 'Mapowania identyfikatorów są nieaktualne') super end end class ImageNotCopiedError < StandardError def initialize(msg = 'Obraz nie został skopiowany podczas kopiowania produktu') super end end
Zajęcia te były dostępne w aplikacji po prostu pod ich nazwą:
podnieś MissingConfigurationKeys
Reguły Zeitwerk nakazywały nam rozbić te klasy na osobne pliki i odwoływać się do nich za pomocą przestrzeni nazw silnika:
# lib/foo/missing_configuration_keys.rb podnieś Foo::MissingConfigurationKeys
7) Jeśli zdecydujesz się włączyć zeitwerk w silniku Rails, kolejność konfiguracji zeitwerk w głównym interfejsie ma znaczenie!
W pierwszej próbie postąpiliśmy zgodnie z README który przedstawia następującą listę kroków:
# lib/foo.rb (plik główny) wymaga „zeitwerk” loader = Zeitwerk::Loader.for_gem loader.setup # gotowy! moduł Foo # … end loader.eager_load # opcjonalnie
Wszystko wydawało się działać prawidłowo, gdy używaliśmy tej sekwencji i montowaliśmy silnik w naszej aplikacji Rails przy użyciu ścieżki Gemfile lokalnego systemu plików.
Po udostępnieniu silnika jako skompilowanego klejnotu, podczas uruchamiania pojawił się komunikat „Foo nie znaleziono”. Walczyliśmy z tym przez kilka godzin, aż w końcu, po niewielkiej refaktoryzacji, nastąpił moment olśnienia: przenieśliśmy ładowanie zeitwerk. po definicja naszego silnika i to załatwiło sprawę:
# lib/foo.rb (plik główny) wymaga modułu klejnotów wewnętrznych/zewnętrznych Foo end require 'zeitwerk' loader = Zeitwerk::Loader.for_gem loader.ignore(“#{__dir__}/active_resource”) loader.setup loader.eager_load
8) Zwiń katalogi, które istnieją tylko w celach organizacyjnych
Bardzo często zdarza się, że katalogi istnieją tylko w celu nadania aplikacji struktury.
Na przykład załóżmy, że masz klejnot API o nazwie „lokalizacja_api” który zawiera pliki „Kraj” i „Miasto”. Każdy zasób ma osobną klasę, która jest zorganizowana jako lib/location_api/resources/country.rb oraz lib/location_api/resources/city.rb
Teraz Zeitwerk będzie oczekiwać kraj.rb wyglądać mniej więcej tak:
moduł Zasoby Moduł Kraj
A teraz, gdy chcesz użyć tego API, musisz wykonać połączenie, np. LocationApi::Resources::Country
To może być w porządku, jeśli tworzysz nowy gem. Ale jeśli masz już taką strukturę przed przejściem do Zeitwerk, wszystkie istniejące wywołania tego gemu API zostaną… LocationApi::Country.
Może to być proste refaktoryzowanie, w którym odniesienia zostaną zmienione na LocationApi::Resources::CountryAle co, jeśli ten klejnot jest używany przez dużą liczbę aplikacji rozproszonych po całej bazie kodu? Nie jest to takie proste, prawda?
Tutaj właśnie pojawia się funkcja składania Zeitwerka:
ładowarka.collapse(“#{__dir__}/location_api/resources”)
Po prostu dodając dyrektywę „zwiń”, kontynuuj używanie interfejsu API w taki sam sposób LocationApi::Country bez konieczności wprowadzania dalszych zmian w aplikacji.
9) Użyj funkcji szybkiego ładowania, aby wyeliminować ukryte problemy
W pliku głównym silnika możesz poinstruować zeitwerk, aby natychmiast załadował twoje pliki, tak jak pokazano we wskazówce nr 7.
Przeciwieństwem ładowania zachłannego jest ładowanie leniwe, które ignoruje klasy, dopóki ścieżka kodu, w której się znajdują, nie zostanie aktywowana przez jakiś sygnał wejściowy (wywołanie API, kliknięcie widoku itp.). Odkryliśmy, że włączenie ładowania zachłannego pomogło nam zwiększyć pewność, że niczego nie pominęliśmy.
To wszystko! 8 porad i trików, które wykorzystaliśmy, aby przejść z klasycznego autoloadera na Zeitwerk w jednym z naszych produktów. Produkt działa teraz sprawnie, wykorzystując Sidekiq 6 i Rails 6 w środowisku produkcyjnym. Odblokował również możliwość aktualizacji naszej wersji Redis!
Jeśli utknąłeś lub nie masz pewności co do aktualizacji autoloadera, mamy nadzieję, że tych 8 wskazówek pomoże Ci uporać się z tym problemem w Twojej aplikacji Rails i być może oszczędzi Ci trochę czasu.
Oto kilka źródeł, które uznaliśmy za przydatne:
- Dokumentacja Zeitwerk na GitHubie:
- Zrozumieć Zeitwerk:
- Niektóre informacje na temat łatania przez małpy lub nadpisywania klas/modułów


