Witajcie.
Jestem nauczony, ze model czyli obiekty domeny (DO, Domain Layer - kt. obiekty sa dostepne vertykalne przez wszystkie warstwy aplikacji Presentation, Services i Persitence) nie powinny zawierac logiki dotyczacej ich manipulacji, chyba, ze te operujace tylko na sobie (jakies konwersje itd.). Czyli nalezy uzywac tzw. DAO (Data Access Objects, nie mylic z antypatternem DTO) enkapsulujacych sposob wydobywania danych.
Ze wzgl. na specyfike ActiveRecord w RoR tego nie ma (I dobrze, niektore javowe ORMy w ten sposob dzialaja, Hibernate uzywa POJO ale to inna bajka…). Wszystkie klasy modelu dziedzicza z jednej klasy, kt, zawiera juz metody odpowiedzialne za dostep do danych, na bazie kt. tworzymy wlasne class method jesli zajdzie taka potrzeba. Zreszta jestem zdania, za wyjatkiem trywialnych odwolan do find (a wrecz te rowniez) wszystkie metody zwiazane z dostepem do danych wrzucac do modelu i wolac je w kontrolerze. Czyli np. realizacja inicjalizacji nowego modelu z jakimis defautowymi wartosciami atrybutow jako class method tegoz modelu. Moze to wydawac sie nadmiarowe ale nadmiarowosc nie jest zla jezeli prowadzi do lepszej separacji logik a przez to aplikacja staje sie mniej “error prone” i generalnie latwiejsza do utrzymania i testowania (tu nawiazuje do postu ruthrsc “Testowanie aplikacji”)
Czyli w RoR klasy modelu enkapsuluja funkcjonalnosc DO i DAO.
Ale teraz pytanie. Gdzie jest warstwa Services/Business Logic? Wiadomo juz, ze czesciowo obiekty modelu (Domain) implementuja czesc funkcjonalnosci (Istnieje zreszta obecnie taka tendencja). Jednak przy wiekszym stopniu skomplikowania domeny jestesmy zmuszeni operowac na wielu DO (nie wszystkie DO musza byc zw. z tabala w bazie). Gdzie umiejscowic te metody?
- Model. Jako class methods modelu. Ale powiazanie jednego modelu z innym logika przeczymy zasadzie “loose coupling” (to oczywiscie nie obejmuje asocjacji zwiazanych z relacjami w bazie - bo wowczas jest to naturalne zwiazanie)
- Controller/Helper - prawdopodobnie lepsze rozwiazanie, ale kontroler jak nazwa wskazuje powinien odpowiadac tylko i wylacznie za sterowanie przeplywem, helper predzej bo bedac modulem mozna sobie jakies klasy serwisowe tam stworzyc.
- /lib - tutaj mozna wrzucic klasy/moduly nie zalezne od modelu a przetwarzajace jedynie jego atrybuty. Mialy by to byc biblioteki, kt. “out-of-box” moga byc wykorzystane przez inna aplikacje bez koniecznosci dostosowywania jej modelu.
- Model (w sensie ze w katalogu /app/models) ale zaimpl. jako specjalna klasa serwisowa, kt. posredniczy pomiedzy kontrolerem a zwiazanymi ze soba logika biznesowa modelami. W ten sposob uzyskujemy “loose coupling” i zachowujemy “separation of concerns”. Czyli nad odpowiednimi DO tworzymy Service Layer.
- Korzystaja z otwartych klas ruby, w nowym pliku (odpowiednio nazwanym) zaimpl. te powiazania czyli de facto to co 1) ale z proba uporzadkowania. Musimy sobie tylko zdac sprawe kt. z klas modelu jest jakby nadrzedna bo z jej poziomu bedziemy manipulowac innymi.
Wydaje mi sie, ze 4) jest najbardziej poprawnym podejsciem. 5) jest ok. Oczywiscie ma to zastosowanie w bardziej zaawansowanych przypadkach. Nic na sile bo ma to nam ulatwic zycie (*).
Czyli w RoR klasa modelu odpowiada rowniez za Service/Businss Logic w prostszych przypadkach, a gdzy zajdzie potrzeba nalezy wprowadzic dodatkowy poziom abstrakcji.
Odn. MVC i struktury aplikacji.
Kazda srednia/wieksza aplikacja powinna miec strukture warstwowa. Czyli jasno wydzielone warstwy Presentation, Business Logic i Persistence. Cale MVC to tylko warstwa jedna warstwa: Presentation. Model z MVC to dane pochodzace z Services/Business Logic ktora przetwarza (w prostych przypadkach po prostu przekazuje) to co przyjdzie z warstwy dostepu do danych (Persistence). Controller natomiast zarzadza tym co ma byc pokazane w View.
W RoR Model pelni role zarowno Business Logic jak i Persistence. Ale logike mozna tez zaimpl. w kontrolerze i wowczas Model to Persistence. Oczywiscie w obydwu przypadkach jest rowniez DO. Stad moje dylematy.
Co sadzicie? Jaka strategie obrac proponujecie?
(*) W javie aby uzyskac “separation of concerns” trzeba stosowac powtarzanie kodu. Bo DAO nie moga nic wiedziec o wykorzystanym frameworku prezentacji (webowym lub nie) i vice versa. wszystko odbywa sie poprzez Service Layer. Ma to znaczenie gdy trzeba zmieniac framework prezentacji z np. Tapestry na JSF czy WebWork, albo zmienic sobie ORMa czy wyemigrowac z Tomcata na server aplikacji bez wiekszych problemow. Jedynymi obiektami dostepnymi wertykalnie sa DO i dlatego m.in. stosuje sie w nich POJO (Hibernate czy EJB3 entity beans). Oczywiscie mowie o duzych projektach.
