Tak, zły! Nie tylko kiepski i nieprofesjonalny. Active Record uczy programistów jak robić kod źle i niechlujnie. Prowadzi projekty ku klęsce.
Zacznijmy od życiowej historii. ;)
Gdyby obiekty istniały w rzeczywistości
Weźmy coś namacalnego. Np. kuchenkę gazową. Nasza kuchenka jest obiektem. Możemy na niej wywołać funkcje. Np:
$kuchenka->włączPalnik(); $kuchenka->zwiększOgień("o 5%");
Proste i logiczne. Ale nasza kuchenka musiała kiedyś do nas przyjechać. Ktoś pobrał ją z magazynu, zapakował i wysłał. Jak to będzie po programistycznemu?
$kuchenka->zapakujSię(); $kuchenka->wyślijDo("Pacanowo");
Stop! źle! Przecież to nie kuchenka się pakuje i wysyła. Mamy sprzedawcę, który to robi. Będzie tak:
$sprzedawca->zapakuj($kuchenka); $sprzedawca->wyślij($kuchenka,"Pacanowo");
To nie kuchenka ma się zapakować i wysłać. To ktoś, kto nią zarządza zajmuje się takimi zadaniami. Sama kuchenka ma robić to, do czego jest stworzona czyli podgrzewać potrawy.
Idąc dalej analogią z obiektami możemy mieć podłączenie kuchenki do gazu za pomocą gwintu 1/2 cala. Kiedy ją instalujemy musimy jej podać złącze spełniające ten interfejs:
public function __construct (\Interfejsy\Gazowe\polCala $gaz);
Wracając do Active Record
A gdzie tu Active Record? Właśnie w pakowaniu i wysyłce. Mając obiekt chcemy zapakować go do bazy. Robimy wtedy:
$obiekt->save();
Ale dlaczego? Nasz obiekt czymś się zajmuje. Ma jakąś ustaloną funkcję. Jest kontem użytkownika, fakturą czy innym przydatnym elementem systemu. Dlaczego jeszcze ma zajmować się zapisywaniem siebie?
Nie powinien. Zasada pojedynczej odpowiedzialności mówi jasno – obiekt ma robić to, do czego jest stworzony i nic więcej. Active Record jest złamaniem tej zasady.
Widziałem złe i bardzo złe implementacje Active Record. W tych lepszych mieliśmy gettery i settery. W tych gorszych, np w Laravel obiekt nie ma pojęcia jakie atrybuty można mu włożyć. Wracając do rzeczywistości, wyobraźmy sobie kuchnię opalaną węglem. Aby zapalić musimy:
// otwórz drzwi do paleniska $kuchnia->otwórzDrzwi(); // wygarnij popiół unset($kuchnia->popiół); // wrzuć nowe drewno czy węgiel $kuchnia->palenisko = $węgiel; // zapal zawartość paleniska $kuchnia->palenisko->podpal // zamknij drzwi $kuchnia->zamknijDrzwi();
Co w tym jest złego? Zwróćmy uwagę, że można włożyć zarówno węgiel jak i drewno. Obiekt nie ma kontroli co jest wkładane. Równie dobrze morzemy wrzucić petardę i polać benzyną. W efekcie dostaniemy wielkie bum. Równie efektowne będzie „bum” naszej aplikacji bez kontroli typów.
Podsumujmy dlaczego Active Record jest zły:
- Wzorzec łamie zasadę pojedynczej odpowiedzialności. Obiekty odpowiadają za co najmniej dwa zadania.
- Wzorzec przywiązuje nas do bazy danych. Każdy obiekt odpowiada jednej tabeli. Nie programujemy obiektowo ale bazodanowo.
- Testowanie jest koszmarem. Jak przetestować całe działanie obiektu jeśli musimy mieć pod ręką bazę danych? Testy jednostkowe są trudne i skomplikowane.
Co zamiast Active Record?
Odpowiedź jest prosta. Data Mapper, który opisywałem niedawno. Korzystając np. z wzorca repozytorium mamy jasny podział ról. Obiekt robi to, co do niego należy. Repozytorium zajmuje się jego przekładaniem, pakowaniem, wysyłaniem i odbieraniem.
Co dalej?
Warto poczytać artykuły dlaczego Active Record jest antywzorcem. Np. ten i ten. Dobre wzorce uchronią nas przed śmietnikiem w kodzie i problemami w przyszłości.