Rails 4.2: ActiveJob, Adequate Record i klucze obce
Release candidate Rails 4.2 jest już dostępny. Spojrzenie na trzy najważniejsze zmiany — ujednolicone API zadań w tle, po cichu duży zysk wydajności w ActiveRecord i klucze obce wreszcie lądujące w migracjach.
Release candidate Rails 4.2 wylądował, a finał niedaleko za nim. To skupione wydanie — trzy sztandarowe funkcje i garść mniejszych — ale każdy nagłówek dotyka czegoś, co programiści obchodzili latami. Uruchamialiśmy bety i oto co 4.2 faktycznie zmienia w sposobie budowania aplikacji Rails.
ActiveJob: jedno API do pracy w tle
Każda nietrywialna aplikacja Rails wypycha wolną pracę w tło — wysyłanie maili, przetwarzanie uploadów, wołanie API stron trzecich. Kłopot zawsze polegał na tym, że biblioteki kolejkowania są niekompatybilne: Sidekiq, Resque, Delayed Job i reszta mają każda własny sposób definiowania i kolejkowania zadania. Zmień backend, a przepisujesz cały kod zadań; napisz gem potrzebujący pracy w tle, a nie możesz wiedzieć, którego backendu używa aplikacja gospodarza.
ActiveJob to naprawia, będąc warstwą adaptera, której Railsom brakowało — jednym, niezależnym od backendu API do deklarowania i kolejkowania zadań:
class ThumbnailJob < ApplicationJob
queue_as :default
def perform(upload)
upload.generate_thumbnail!
end
end
ThumbnailJob.perform_later(upload) # zakolejkuj
ThumbnailJob.set(wait: 1.hour).perform_later(upload) # zakolejkuj, z opóźnieniem
Piszesz zadania raz wobec ActiveJob, a jednolinijkowa konfiguracja wybiera faktyczny backend kolejki:
config.active_job.queue_adapter = :sidekiq
Zamień :sidekiq na :resque albo :delayed_job, a nie zmienia się ani linia
kodu zadań. Dwie rzeczy czynią to czymś więcej niż cienkim opakowaniem. Po pierwsze,
GlobalID: ActiveJob serializuje obiekty ActiveRecord globalnym identyfikatorem i
przeładowuje je w workerze, więc możesz przekazać model do perform_later i
otrzymać model (a nie surowe id) w perform — drobna wygoda usuwająca sporo
boilerplate’u. Po drugie, integracja z Action Mailer: deliver_later wysyła
teraz mail przez ActiveJob, więc „wyślij ten mail w tle” to wreszcie jednolinijkowiec
na poziomie frameworka, niezależny od backendu, zamiast kleju per-backend.
Uczciwe zastrzeżenie: ActiveJob to abstrakcja nad backendami, które wciąż różnią się realnie (ponawiania, obsługa martwych zadań, współbieżność). Standaryzuje interfejs, nie każde zachowanie, więc wciąż wybierasz Sidekiqa albo Resque ze względu na ich faktyczne cechy — tylko przestajesz wiązać kod zadań z tym wyborem.
Adequate Record: darmowa wydajność
Najbardziej zuchwale nazwana funkcja jest też tą, do której zdobycia nie robisz nic. „Adequate Record” to znacząca wewnętrzna optymalizacja ActiveRecord cache’ująca wyliczony SQL i prepared statements dla częstych wzorców zapytań. Za pierwszym razem, gdy uruchamiasz konkretny kształt zapytania, AR wykonuje pracę jego zbudowania; kolejne zapytania tego samego kształtu reużywają cache’owany plan i pomijają część tego narzutu.
# DRUGIE i późniejsze wywołania tego kształtu są zauważalnie szybsze,
# bo wygenerowany SQL / prepared statement jest cache'owany
Post.find(1)
user.posts.where(published: true).to_a
Raportowane przyspieszenia na częstych operacjach jak find i ładowanie asocjacji
to mniej więcej 2× poprawa, a dostajesz je przez aktualizację — bez zmian kodu, bez
nowego API. Dla aplikacji mocno czytających, wykonujących te same kształty zapytań
miliony razy, to znacząca, darmowa wygrana. To dokładnie ten rodzaj nieefektownej
pracy w rdzeniu, który czyni aktualizację Railsów wartą jej, nawet gdy lista
nagłówkowych funkcji wygląda krótko.
Klucze obce w migracjach, nareszcie
Ta jest spóźniona i bardzo mile widziana. Przez lata ActiveRecord pozwalał deklarować asocjacje w modelach, zostawiając bazę bez faktycznego ograniczenia klucza obcego — więc integralność referencyjna była egzekwowana (w najlepszym razie) przez kod aplikacji i całkiem nieobecna na poziomie bazy. Usuń rekord, na który wskazywał inny wiersz, a baza wzruszy ramionami; zostawałeś z sierotami i wiszącymi referencjami, które jakaś walidacja miała powstrzymać, ale przy współbieżności lub zabłąkanym skrypcie nie powstrzymała.
4.2 wnosi klucze obce do DSL migracji jako pierwszoklasowych obywateli:
class AddForeignKeys < ActiveRecord::Migration
def change
add_foreign_key :comments, :posts
add_foreign_key :comments, :users, on_delete: :cascade
end
end
# albo inline przy tworzeniu tabeli:
create_table :comments do |t|
t.references :post, foreign_key: true
t.references :user, foreign_key: true
end
Teraz baza gwarantuje, że komentarz nie może wskazywać na nieistniejący post, a
on_delete: :cascade pozwala zepchnąć reguły usuwania tam, gdzie ich miejsce. To
baza robiąca robotę, w której jest wyjątkowo dobra — egzekwowanie niezmienników,
które muszą zachodzić bez względu na to, jaki kod, skrypt czy wyścig próbuje je
złamać. Długo dodawaliśmy je ręcznie w surowych migracjach SQL; posiadanie ich w DSL
oznacza, że są teraz łatwą, domyślną ścieżką, a więcej aplikacji dostanie
integralność, którą powinny były mieć od początku. (Postgres i MySQL to dostają;
wsparcie SQLite jest ograniczone, jak zwykle.)
Mniejsze dodatki
Jeszcze kilka wartych odnotowania: Web Console daje ci interaktywną sesję irb
wprost w przeglądarce na stronie błędu (i przez console w widoku) — naprawdę
użyteczne wsparcie debugowania, choć takie, które trzymaj zdecydowanie z dala od
produkcji. render zyskuje możliwość renderowania szablonów spoza własnych ścieżek
widoków kontrolera. I jest zwykła runda usprawnień ActiveSupport oraz porządków z
deprecjacjami, gdy framework dalej się sprząta.
Werdykt
4.2 to wysoko wartościowa, niskotarciowa aktualizacja. ActiveJob to standaryzacja,
której przetwarzanie w tle zawsze potrzebowało, i od razu czyni twój kod zadań
przenośniejszym, a mailery łatwiejszymi; Adequate Record wręcza ci realną poprawę
wydajności za cenę bundle update; a klucze obce zamykają lukę poprawności, która
po cichu gryzła aplikacje Rails przez dekadę. Nic z tego nie wymaga przepisywania
czegokolwiek — wdrażaj ActiveJob, dotykając swoich zadań, dodawaj klucze obce w
następnej migracji i ciesz się przyspieszeniem za darmo. To Rails dojrzewający w
najlepszy sposób: nie goniący nowości, lecz naprawiający rzeczy, które doświadczone
zespoły nauczyły się obchodzić.