Brutalne prawdy o MVC
MVC jest obecnie bardzo popularnym wzorcem stosowanym w chyba każdej technologii służącej do tworzenia aplikacji webowych. Zarówno w .NET, PHP, Javie, jak i bardziej hipsterskich językach stał się on pewną podstawą, bez znajomości której nie ma szans na znalezienie pracy. Z moich doświadczeń wynika jednak, że MVC jest chyba najbardziej nierozumianym wzorcem. Nierozumianym chyba nawet bardziej niż repozytorium. Powszechne niezrozumienie MVC zauważył i opisał już jakieś 10 lat temu Martin Fowler. Świadczy to chyba o tym, że nasza cywilizacja przestała sie już rozwijać.
Uwaga - ten artykuł zawiera bardzo dużo faktów obalających powszechne panujące przekonania. Może zniszczyć i tak ciężkie dzieciństwo senior developerów. Zaglądasz na własne ryzyko!
1. Framework to nie jest wzorzec
W świecie .NET najpopularniejszym frameworkiem do tworzenia aplikacji webowych jest ASP.NET MVC. Ostatnie trzy litery tej nazwy powinny sugerować, że służy on do tworzenia aplikacji zgodnych z wzorcem MVC. Niestety, podobnie jak pisanie w języku obiektowym nie czyni kodu automatycznie obiektowym, tak samo korzytanie z frameworka z MVC w nazwie, nie sprawia, że aplikacja jest zgodna z tymże wzorcem. W projektach opartych o MVC biorę udział od jakichś sześciu lat. W dwóch miastach, kilku firmach i ponad dziesięciu projektach, widziałem raptem jeden, który był zgodny z wzorcem. (Za to był tak niemożebnie skopany pod każdym innym względem, że wolałbym go zapomnieć. ;)) Skąd się to bierze? Mam nadzieję, że następne punkty wyjaśnią (chociaż nie usprawiedliwią).
2. MVC vs WebForms
W świecie .NET szał na punkcie “nowego wzorca” MVC pojawił się w momencie, gdy Microsoft wypuścił swoją implementację tego frameworka, czyli w marcu 2009 roku. Istniały wcześniej alternatywne, opensourcowe frameworki wspierające ten wzorzec (np. FubuMVC czy Castle MonoRail), ale dopiero w momencie, gdy szablon projektu dało się wyklikać w Visual Studio, programiści .NET rzucili się na MVC jak Reksio na salceson. Równie szybko znienawidzili jedyną sluszną wcześniejszą technologię wspieraną przez Imperium, czyli WebFormsy. Niemal natychmiast zostały one uznane za nietesowalne, nieskalowalne i “używane przez nienormalne firmy” (tak przynajmniej pisali znani blogerzy na forum CodeGuru.pl). WebFormsy = zło - wiedzą to wszyscy oprócz niektórych dotnetowych dinozaurów zabetonowanych w swoich piwnicach.
Fakty jednak są takie, że dobrze zaprojektowana aplikacja WebForms, oparta o wzorzec Model View Presenter i posiadająca sensowny podział na warstwy, jest znacznie lepszym rozwiązaniem niż mainstreamowa implementacja ASP.NET MVC, z repozytoriami wstrzykniętymi do kontrolerów, i modelbinderami odstawiającymi takie cuda, że bez odpalenia IIS nie da się przetestować logiki biznesowej. O jakości aplikacji świadczy archiektura i kod, nie użyta technologia.
3. To nie Microsoft wynalazł MVC
To chyba najbardziej brutalna prawda z zebranych tutaj. Oczywiście, z punktu widzenia przeciętnego programisty, całkiem logiczne i naturalne jest uznanie, że skoro jakaś opcja pojawiła się w IDE Microsoftu, to znaczy, że jej autorem jest ta właśnie firma. Niestety, prawda jest taka, że MVC istniał w czasach, gdy w Microsofcie szczytowym osiągnięciem nie był nawet jednozadaniowy system operacyjny, a co dopiero konsola do gier, która nie potrafi przechowywać i udostępniać zrzutów ekranu.
MVC powstał tam, gdzie wiele innych kamieni milowych komputeryzacji, takich jak: mysz, ikony i graficzny interfejs użytkownika, czyli w laboratoriach XEROX PARC. Jest to całkiem logiczne - skoro mieli te wszystkie rzeczy, to naturalnie wystąpiła potrzeba wymyślenia, jak dobrze zaprojektować program, który będzie potrafił wyświetlić dane w trybie graficznym, zinterpretować polecenia użytkownika wydane za pomocą klikania myszą w przyciski na ekranie oraz wykonać jakąś tam logikę biznesową. I tu z pomocą przyszedł pewien norweski uczony, który w latach 1978/79 akurat tam przebywał.
4. Tutorial to nie jest źródło wiedzy o architekturze
Każdy programista wie, czym jest tutorial. Tutorial to takie coś, co ma pokazać przykład zastosowania jakiejś technologii/frameworka/języka. W tutorialu chodzi o pokazanie podstawowych koncepcji, najprostszego możliwego przykładu wykorzystania - bez zaśmiecania dodatkowymi informacjami, innymi wzorcami, nadmiernymi konstrukcjami, warstwami. To musi być najprostszy możliwy przypadek użycia - po to, aby programista uczący się nowego narzędzia mógł łatwo załapać, jak je stosować. Wiadomo przecież, że taki maksymalnie uproszczony przykład użycia jakiejś technologii nie jest wzorem dla tworzenia rzeczywistych aplikacji, prawda?
Nie, nieprawda. Tutoriale Microsoftu prezentujące MVC stały się główną wyrocznią jeśli chodzi o rozumienie tego wzorca. Ludzie, którzy swoją wiedzę na temat MVC czerpią wyłącznie z tych tutoriali, są przekonani, że kontroler to miejsce na logikę biznesową, a model to wyłącznie dane… No bo tak jest przecież w tutorialu! A gdyby to nie była prawda, to ogromna korpoacja nie umieszczałaby takich informacji na swojej stronie. Wnioskowanie nie do obalenia.
5. Model to nie są dane
Pogląd, jakoby model był wyłącznie danymi, to jedna z najczęściej powtarzanych nieprawd na temat MVC. Pod wpływem przykładów z internetu ludzie są przekonani, że model to klasy mapowane przez ORMa na tabele bazy danych. Mają one nie zawierać logiki, jedynie właściwości (czyli de facto być anemicznym modelem). Te same klasy mają być też wyświetlane przez Widok.
Tymczasem, jest to tak odległe od prawdy, jak to tylko możliwe. Model to warstwa. A nawet wiele warstw. Model to wszystko to, co nie jest widokiem ani kontrolerem. Model to domena/dziedzina aplikacji. Model to niezależny byt, który realizuje logikę biznesową samodzielnie, bez żadnych odwołań do technologii prezentacji. Model to nie są wyłącznie dane. Model to serwisy domenowe, encje, i repozytoria, albo np. skrypty transakcji.
A Model is an active representation of an abstraction in the form of data in a computing system The models are represented in the computer as a collection of data together with the methods necessary to process these data
The model of an application is the domain-specific software simulation or implementation of the application’s central structure.
Finally, the model manages the behavior and data of the application domain, responds to requests for information about its state (usually from the view), and responds to instructions to change state (usually from the controller).
In MVC, the domain element is referred to as the model. Model objects are completely ignorant of the UI. To begin discussing our assessment UI example we’ll take the model as a reading, with fields for all the interesting data upon it.
6. Kontroler nie jest najważniejszy
Odpowiedzialność kontrolerów to druga z najczętszych nieprawd, występujaca zazwyczaj razem z poprzednią. Ponieważ na ogół stosowane są anemiczne modele, to z braku innego miejsca, procesy biznesowe, import z CSV, eksport do PDF, generowanie wykresów, operacje na bazie danych, walidacja i wszystko to, czego aplikacja potrzebuje, musi zostać umieszczone do kontrolerach. Z doświadczenia wiem, że w kontrolerze dużo się zmieści, 10 tysięcy linii kodu to nie jest żaden problem dla szybkopiszącego programisty.
W końcu Kontroler brzmi jak coś, co ma wszystko kontrolować, więc to musi być miejsce na te wszystkie operacje. Brzmi logicznie! Jest to wręcz kanoniczny przykład SRP - w końcu jedyną odpowiedzialnością takiego “kontrolera” jest kontrolowanie wszystkiego. Niczego nie wyświetla, niczego nie składuje w bazie, jedynie zajmuje się całą resztą. +100 punktów do znajomości SRP w CV.
Niestety, prawda jest zgoła inna. Kontrolery powinny być jak najprostsze, obsługiwać jedynie żądania użytkownika, wywoływać operacje na Modelu i przekierowywać do odpowiednich Widoków. Kontrolery nie są miejscem na logikę biznesową, bo jak już wiemy z poprzedniego punktu, ta znajduje się w Modelu. Kontrolery nie maja operować na repozytoriach (bo to logika biznesowa), ani tym bardziej na kontekście ORMa,bo to należy do warstwy dostępu do danych.
Controllers contain the interface between their associated models and views and the input devices (keyboard, pointing device, time).
The controller interprets the mouse and keyboard inputs from the user, commanding the model and/or the view to change as appropriate.
The controller’s job is to take the user’s input and figure out what to do with it.
Kontroler to po prostu ta część warstwy prezentacji, która odpowiada za obsługę wejścia od użytkownika. Druga część warstwy prezentacji, czyli widok, odpowiada za wyjście do użytkownika. Koniec dyskusji.
7. View nie powinen wyświetlać encji
Używam słowa “encja” w potocznym tego słowa znaczeniu. Sam encjami nazywam tylko encje z DDD albo elementy diagramu ERD przekształcane później do tabel. Anemiczna encja to przecież nie encja, bo nawet nie jest obiektem w sensie OOP…
Jak już ostatnio pisałem, zazwyczaj nie potrzebujemy pokazywać w GUI wszystkich pól danego rekordu i zazwyczaj dane, które chcemy pokazać, nie pochodzą z jednej tabeli, lecz są projekcją wielu z nich. Dlatego do widoków powinno przesyłać się obiekty klas zawierających tylko te dane, które są na widoku potrzebne. Podobnie ma sie sprawa z edycją - obiekt reprezentujący dane uzupełnione przez użytkownika w formularzu tworzenia czy edycji rekordu, nie powinien być obiektem mapowanym do tabeli, ani tym wyświetlanym w trybie do odczytu. Tu nie ma nic odkrywczego, to po prostu trzymanie się zasady SRP - jedna klasa powinna mieć jeden cel. Przechowywanie danych w bazie, wyświetlanie ich użytkownikowi oraz edycja, to są trzy cele, więc wymagają trzech klas.
Zdaniem niektórych takie podejście prowadzi do złamania zasady DRY, bo w efekcie mamy np. trzy klasy CustomerCośtam i każda ma jakąś część właściwości taką samą. Tyle tylko, że w DRY chodzi o niepowtarzanie fragmentów kodu, których powielenie utrudnia wprowadzanie zmian w aplikacji, a nie tworzenie na siłę jednego bytu z trzech. Opisywany przypadek - oddzielnych modeli do różnych zadań, ma na celu to samo co DRY - ułatwianie rozwoju aplikacji. Przy oddzielnych modelach nie ma ryzyka, że usunięcie jakiejś właściwości wyświetlanej na ekranie spowoduje zniknięcie kolumny z bazy danych, albo że dodanie nowego klucza obcego spowolni ładowanie listy rekordów na skutek wystąpienia problemu n + 1. Przy popularnym podejściu z jedną klasą, nie takie cuda się dzieją.
8. MVC nie czyni aplikacji trójwarstwową
MVC właściwie ma nic do warstw, bo VC to jedna z nich - warstwa prezentacji, a M może być nawet tysiącem warstw - w zależności oczywiście od rozmiaru systemu i poczucia humoru architekta. Jak chcemy mieć warstwy w aplikacji to separujmy infrastrukturę, dostęp do danych, logikę biznesową, logikę aplikacyjną, a nie liczmy na to, że wszystko załatwi wzorzec projektowy warstwy prezentacji.
Czy można prościej?
Oczywiście można napisać aplikację opartą na grubych kontrolerach, które realizują logikę biznesową i używają repozytoriów albo nawet bezpośrednio kontekstu ORMa. Takie uproszczenie archiektury może mieć jakiś sens - pozbycie się warstw powinno skrócić chociażby czas implementacji. Możliwe, że problemy wydajnościowe, przed którymi przestrzegałem wcześniej nie będą dokuczliwe, może w przypadku krótkotrwałego projektu nie będzie też problemu z rozwojem aplikacji.
Tylko w takim przypadku nie można twierdzić, że taki kod jest zgodny z MVC. To nie jest ten wzorzec, nie i kropka. Czym innym jest świadome nagięcie jakiegoś wzorca, a czym innym jego łamanie wynikające z braku wiedzy i uporu w jej zdobywaniu.
Inna rzecz, że upraszczać można też na sposoby, które MVC nie łamią. Tak jak ja to robię, np. w Pizzy.