Wprowadzenie

Wzorzec ten budzi wiele kontrowersji co do tego czym są te trzy komponenty. Popatrzmy do anglojęzycznej WIKI:

Model – Centralny komponent wzorca. Jest to dynamiczna struktura danych aplikacji, niezależna od interfejsu użytkownika. Bezpośrednio zarządza danymi, logiką i regułami aplikacji. W Smalltalk-80, model jest całkowicie pozostawiony programiście. W WebObjects, Rails i Django, model zazwyczaj reprezentuje tabelę w bazie danych aplikacji.

https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

Jest to dominująca definicja w literaturze, zaś spory o to czym jest Model w architekturze MVC jak widać mają jedno główne źródło: jakiego frameworka używa osoba wchodząca w takie spory. WIKI Cytuje to opracowanie: https://medium.com/free-code-camp/simplified-explanation-to-mvc-5d307796df30, zawierające między innymi tę ilustracje wyjaśniającą o co chodzi:

“Model to dynamiczna struktura danych aplikacji, niezależna od interfejsu użytkownika. Bezpośrednio zarządza danymi, logiką i regułami aplikacji.

Z czym sie spotykamy

Popatrzmy na popularne “deweloperskie” artykuły:

To klasyczna wersja bazująca na popularnym podejściu, mówiącym, że Model to dane a Controller to logika biznesowa a View to aktualny stan tej bazy danych. Autor wprost pisze: sets data via setters/getters (tak przy okazji set/get w klasach to jednak z najgorszych praktyk projektowania obiektowego: pobranie dowolnego dokumenty wymaga nie raz wywołania kilkudziesięciu operacji, łamiemy hermetyzację, itp.).

Powyższe to proste podejście znane ze starych strukturalnych narzędzi deweloperskich operujących trzema warstwami (np. Oracle Forms, JavaEE czy ASP.NET):

I warstwy te utożsamiane są odpowiednio z View (Presentation Tier), Controller (Business Tier) oraz Model (Data Tier). Baza SQL (relacyjna) to implementacja utrwalania. Podejście to nadal jest obecne w wielu frameworkach mających rodowody w latach 80/90 ubiegłego wieku.

Kolejnym błędem jest utożsamianie MVC z wzorcem BCE (Boundary, Control, Entity). Wzorzec ten ma swój rodowód oparty na polityce projektowania zorientowanej na odpowiedzialność klas [Wirfs-Brock, R., & McKean, A. (2006). Object Design: Roles, Responsibilities and Collaborations,. 88.]:

Istota tego wzorca to przyjęcie prostej zasady mówiącej, że klasa ma jedna wąską odpowiedzialność i są to trzy bazowe, alternatywne specjalizacje: interface (Boundary) zamykająca wejście do środka komponentu, logika (Control) realizująca przetwarzanie (przechowuje algorytmy i metody) oraz utrwalanie danych reprezentujących obiekty biznesowe (Entity). Czyli np. walidator formularza będzie klasą typu Control, a sam formularz (cały) będzie przechowywany w klasie Entity.

Popatrzmy teraz na to:

Autor używa tych symboli by pokazać trzy warstwy: GUI, logika, baza danych. Jeżeli jakiekolwiek aplikacje mają taką architekturę, są potężnymi monolitami.

Architektura heksagonalna

W 2005 roku Cocburn opisał podejście do architektury, które nazwał: Architektura heksagonalna [Cockburn, A. (2005, January 4). Hexagonal architecture. Alistair Cockburn. https://alistair.cockburn.us/hexagonal-architecture/]. Idea tego pomysłu polega na podziale kodu aplikacji na dwa obszary: Aplikacja, czyli cały i tylko kod realizujący funkcjonalność oprogramowania (w tym to jak i jakimi danymi zarządza) oraz reszta, czyli środowisko w jakim ten kod sie wykonuje (zwane kiedyś Run Time). To środowisko jest widziane jako adaptery (API) udostępniające pozafunkcjonalne elementy takie jak nośniki danych, usługi wysyłania odbierania komunikatów (SMS, email, itp.), dostęp do sieci Internet, usługi VPN, Interfejs użytkownika, drukarki itp. Schematycznie wygląda to tak:

Cockburn, A. (2005, January 4). Hexagonal architecture. Alistair Cockburn. https://alistair.cockburn.us/hexagonal-architecture/

Bardziej precyzyjnie zobrazowano to na tym schemacie:

źr.: https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/

Co mamy na powyższym diagramie? Separacja trzech obszarów: kod realizujący dialog z człowiekiem po lewej (User Interface), kod funkcjonalność aplikacji w środku obramowany czerwona linią (Application Core) oraz kod realizujący wszelkie pozostałe pozafunkcjonalne usługi (Infrastructure). W tej kolejności były by to: V-M-C. Z uwagi (chyba na wygodę wymowy albo ważność tych elementów) piszemy MVC. Niektórzy autorzy w miejsce litery M wstawiają słowo Model (Domain Model).

Konsekwencje

W konsekwencji wyłania się podział działającej aplikacji na dwie zasadnicze części: komponent realizujący wymaganą funkcjonalność aplikacji oraz środowisko w jakim ten komponent funkcjonuje czyli biblioteki, sterowniki itp. ogólnie zwane adapterami:

Aplikacja i jej środowisko wykonawcze.

Nie przypadkiem jedną z pierwszych prac dewelopera jest zestawienie “stosu technologicznego” czyli środowiska w jakim aplikacja (jak zostanie napisana) będzie się wykonywała.

Z innej perspektywy można to pokazać tak:

Komponenty View, Controller, Model i ich standardowe zależności.

A teraz wyobraźmy sobie, że nasza aplikacja obejmuje obszar oznaczony turkusowym kolorem:

To jaki to jest wzorzec architektoniczny? Tak, nadal są w użyciu takie frameworki zwane “legacy architecture”. Nazywane czasami sarkastycznie “rozsmarowaniem logiki po całości”.

Podsumowanie

Tak więc architektura MVC to architektura całości działającej aplikacji gdzie Model to (makro)komponent realizujący jej funkcjonalności. Wzorzec BCE to polityka modelowania architektury komponentu na niskim poziomie (pojedyncze klasy/obiekty).

Ścisłe separowanie kontekstów i odpowiedzialności komponentów to kluczowa zasada metod obiektowych: hermetyzacja. Jakiekolwiek mieszanie odrębnych obszarów logiki i wymagań szybko doprowadza dowolny projekt do postaci monolitu.

Na koniec zacytuję ciekawy artykuł:

The products we build should never aim to reflect reality literally. The (physical) reality is unbound and infinitely complex – most of its details are just noise & distractions. We need its simplified, filtered version – these parts (/aspects) of the reality that are relevant in the current context. DDD calls it a “model”.

The failed promise of Domain-Driven Design

Warto także spojrzeć na opis w ang. WIKI:

Opisany jako:

Model-widok-kontroler (ang. MVC) […] Elementy te to wewnętrzne reprezentacje informacji (model), interfejs (widok), który prezentuje informacje użytkownikowi i przyjmuje je od niego, oraz oprogramowanie kontrolera łączące te dwa elementy.

https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

Warto tu przypomnieć, że “wewnętrzne reprezentacje informacji (model)” to nic innego jak dane i logika ich przetwarzania a nie “baza danych”. To dlatego niektórzy autorzy przywołują tu architekturę heksagonalną, która zakłada wydzielenie realizacji wymagań funkcjonalnych jako osobnego komponentu: to jest właśnie komponent Model.

Przykład

Etapy wytwarzania oprogramowania:

Umowa na wytworzenia oprogramowania to model przypadków użycia:

Kontekst i zakres projektu: separacja specyfikowania usług.

Prostokąt “Aplikacja dla Biblioteki” to cała działająca aplikacja. W projekcie jest to Model, bo pierwsze kluczowe założenie to: nie będziemy pisali kodu obsługującego sprzęt i standardowe elementy wejścia i wyjścia, bo to daje nam środowisko (system operacyjny i framework, itp.). Dlatego skupiamy się na zaprojektowania Mechanizmu działa aplikacji (zwana częścią dziedzinową).

Najpierw projektujemy architekturę HLD (High Level Design):

Architektura HLD: separacja implementacji usług.

Na tym modelu mamy podział na komponenty, wewnętrzną integrację i integracją z innymi aplikacjami. Na tym poziomie głównym wzorcem są mikroserwisy i wzorzec saga. Na tym etapie podejmujemy decyzje, które komponenty kupujemy na rynku, a które tworzymy sami. Na tym poziomie testujemy także scenariuszy przypadków użycia. Np. wypożyczenie:

Poglądowy scenariusz HLD dla usługi wypożyczenia: separacja odpowiedzialności.

Kolejny krok to kolejne projektowanie każdego komponentu, który tworzymy sami. Przykład jednego z nich:

Architektura LLD: separacja funkcji.

Na tym poziomie są bazowym wzorcem jest BCE oraz podstawowe wzorce: łańcuch odpowiedzialności, repozytorium, envelope.

Całość na każdym etapie testujemy (komunikacja, scenariusze) diagramami sekwencji. A gdzie tak popularne “diagramy klas”? Dane i ich logikę modelują formularze:

Model struktury dokumentów, formularzy, komunikatów: separacja kontekstów.

Jeżeli wymagane jest opisane metody (algorytmu) realizacji operacji obiektu, tworzymy algorytm:

Algorytm, procedura: separacja metod.

Wszystko to powyżej realizuje komponent Model. Reszta to wymagania pozafunkcjonalne i infrastruktura (komponenty View i Controller). Aż tyle i tylko tyle.

Powyższa dokumentacja opisuje to jak to wszystko działa (cała logika biznesowa w jednym miejscu). Po wydruku będzie miała nawet tysiąc razy mniejszą objętość niż pełny wydruk kodu źródłowego jaki ona opisuje. Jest także niezależna od metod implementacji. Jak sądzisz czytelniku, co będzie czytane przez nowych członków zespołu, urzędnika Urzędu Patentowego czy inwestora przejmującego aplikację? Ja sądzisz, lepiej użyć notacji UML, która de facto zna cały świat czy jakiejś niszowej metody znanej niemalże tylko autorowi tych dokumentów?

A gdzie są mityczne diagramy klas pełne drzew dziedziczenia i kompozycji? Nie ma bo są bezwartościowe, a jeżeli faktycznie ktoś tak koduje, to są to mega kosztowne monolity…

Jeżeli Twój system lub jego projekt jest podobny do poniższego schematu, to znaczy, że Twój projekt jest jednak porażką…

Jak tego nie robić…

Jarosław Żeliński

Jarosław Żeliński: autor, badacz i praktyk analizy systemowej organizacji: Od roku 1991 roku, nieprzerwanie, realizuje projekty z zakresu analiz i projektowania systemów, dla urzędów, firm i organizacji. Od 1998 roku prowadzi samodzielne studia i prace badawcze z obszaru analizy systemowej i modelowania (modele jako przedmiot badań: ORCID). Od 2005 roku, jako wykładowca akademicki wizytujący (nieetatowy), prowadzi wykłady i laboratoria (ontologie i modelowanie systemów informacyjnych, aktualnie w Wyższej Szkole Informatyki Stosowanej i Zarządzania pod auspicjami Polskiej Akademii Nauk w Warszawie.) Oświadczenia: moje badania i publikacje nie mają finansowania z zewnątrz, jako ich autor deklaruję brak konfliktu interesów. Prawa autorskie: Zgodnie z art. 25 ust. 1 pkt. 1) lit. b) ustawy o prawie autorskim i prawach pokrewnych zastrzegam, że dalsze rozpowszechnianie artykułów publikowanych w niniejszym serwisie jest zabronione bez indywidualnej zgody autora (patrz Polityki Strony). Konsultacje: wszelka pomoc i wyjaśnienia dotyczące treści artykułów autora bloga udzielana jest wyłącznie w ramach płatnych konsultacji.

Ten post ma 16 komentarzy

  1. Krzysztof

    Super materiał.

  2. Sławek Sobótka

    Z MVC jest totalny burdel. Nawet cytat z wiki jest nielogiczny, bo z jednej strony model ma logikę a z drugiej model to tabelki (ew. encje je odzwierciedlające w klasach). Czasem MVC jest architekturą aplikacyjną całej aplikacji, czasem tylko warstwy prezentacyjnej. Czym to skutkuje… W pierwszym podejściu kontroler zajmuje się wszystkim: walidacją, kontrolą przepływu pomiędzy widokami i logiką biznesową (bo encje anemiczne, bo tutorial do frameworka musi być dopasowany dla ludzi z dwucyfrowym iq). W drugim podejściu model jest modelem prezentacyjnym, kontroler zajmuje się kontrolą przepływu gui, a pod warstwą ui mamy osobne warstwy czy pierścienie hexagonu zależnie od poziomu złożoności logiki. Ale kiedy zajrzeć do materiałów z lat 80 to model nie musiał być anemiczny. Dalej mamy wariacje na temat MVC: MVVM, MVP, PAC itd, które próbują rozwiązać problem umiejscowienia i rodzajów logiki. A od tego mim zdaniem trzeba zacząć: jakie są rodzaje logiki? Kolejno od góry, czyli od intencji usera: walidacja syntaktyczna, walidacja biznesowa, logika procesu i jego stanu, logika integracjim logika flow ui, logika biznesowa: spójność zmiany stanu biznesu (nie procesu), obliczenia/transformacje, spójność zmiany danych. Więc pojawia się pytanie: w który rodzaj logiki angażuje się analityk? Na koniec pytanie o przykład z biblioteką, bo wygląda on na zorientowany na obieg dokumentów ale nie widać jakie są reguły dziedziny, które decydują jak te dokumenty obsługiwać. Coś jak wnioski w urzędzie: wnioski są api petenta aby nie znał domeny urzędnika. Ale wracając do pytania: kiedy wypożyczam książkę, to zmienia się stan zasobów biblioteki i zmienia się stan mojej karty bibliotecznej. Gdzie jest logika koordynujące te dwie zmiany?

    1. Jarosław Żeliński

      Oczywiście, że z MVC jest “burdel” każdy dostawca frameworka “rozumie go po swojemu”. Jest dokładnie tak jak napisałeś, dlatego wielu ludzi “sięga do źródeł” abstrakcji.

      Na to dodatkowo nakłada się bardzo ważny problem jakim jest przenoszalność aplikacji i dokumentowanie (w tym ochrona wartości intelektualnych). I tu pojawia sie “rozwiązanie”:

      – to co kupujemy jako narzędzie i środowisko to “tool”, to dostajemy i używamy a nie tworzymy,

      – jeżeli odseparujemy to co tworzymy od tego co dostajemy (środowisko), to wiemy co ewentualnie będziemy przenosić na inną platformę, do innego języka programowania (z tego co mówił A. Cockburn na jednym z niedawnych webinariów, to właśnie mu przyświecało gdy pisał doktorat o architekturze heksagonalnej)

      – w konsekwencji dokumentacja całego systemu to TYLKO opis logiki ale całej czyli wraz z danymi (Model) i dołączona dokumentacja użytego środowiska.

      Dlatego “z góry patrząc”:
      – środowisko wykonawcze to Controller (sesje, wszelkie API do sprzętu, podawanie aktualnego czasu czy ID zalogowanego, itp)
      – intepretowanie komunikatów z Modelu dla ludzi (wymiana treści) to View (i tu niekończące się dyskusje o pakowaniu logiki biznesowej do widoków)
      – serce aplikacji (jej cała funkcjonalność) to Model, który można (powinno sie dać) w dowolnym momencie przenieść w inne środowisko.

    2. Jarosław Żeliński

      “jakie są rodzaje logiki?”

      1. dane są z “ludzkiej” perspektywy grupowane w dokumenty/komunikaty (formularze)
      2. walidujemy pola tych formularzy
      3. jeżeli są związki między polami (jednego lub wielu formularzy) są to reguły, a bywa, że złożone algorytmy

      i to jest 100% logiki realizowanej przez Model. Reszta to pozafunkcjonalne problemy rozwiązywane po stronie infrastruktury.

  3. Sławek Sobótka

    Co w przypadku, kiedy mamy system z “głęboką” logiką, czyli dane z formularzyków, to tylko przekazanie małej ilości parametrów wejściowych. Pod spodem jest kilka razy więcej danych pobieranych z bazy i innych źródeł, masa logiki i odwołań do innych serwisów (dla utrudnienia zdalnych, które mają swole SLA, więc logika kompensacji dochodzi, biznesowej kompensacji). Słynne “kup teraz” na allegro. Czym wtedy będzie model wyrażony?

    1. Jarosław Żeliński

      Głęboka logika to mity albo dramat. Dramat jest gdy wpakujemy logikę do infrastruktury, wtedy jest masakra.
      1. formularze/komunikaty to i tak jedyna forma komunikacji
      2. jedna SQL baza danych “pod spodem” to właśnie masakra logiki umieszczonej w infrastrukturze
      dalej
      3. dowolny system to skończona liczba “agregatów danych” i logiki komunikacji między nimi
      4. wymiana danych między agregatami to sekwencje, z perspektywy systemu każde API (SLA nie ma tu nic do rzeczy) do zewnętrznego systemu to kolejny agregat
      5. słynne “kup-teraz” to prosta realizacja natychmiastowego zamknięcia aukcji (status agregatu) i wystawienia faktury na bazie danych z tego agregatu (czyli kolejny agregat)

      Model to całość tego co opisałem powyżej, reszta to infrastruktura, która nie powinna zawierać jakiejkolwiek logiki biznesowej.

  4. Sławek Sobótka

    Ok, to w przykładzie biblioteki, gdzie mamy zasoby biblioteki i karty. Co będzie agregatami, jak będą się komunikować i gdzie będzie logika zapewnienia, że zmienią się one spójne a wrazie gdyby nie (system rozproszony) to coś będziemy kompensować?

    1. Jarosław Żeliński

      1. agregatem są: karta wypożyczenia, karta zasobu, karta czytelnika
      2. agregaty z zasady sie między soba nie komunikują (to antywzorzec), to koordynator (Saga) czyta i zapisuje komunikaty z dziedzinowych komponentów zawierających agregaty
      3. nadal nie wiem co to takiego ta mityczna kompensacja
      4. to, że system jest rozproszony nie ma tu żadnego znaczenia.

      powyższe masz pokazane w podsumowaniu artykułu.

  5. Sławek Sobótka

    Luz, ja wiem jak to zrobić na kilka sposobów:) Naprowadzam tylko na to, że trzeba dodać nowe klocki do tego mvc, bo niewiele z samego mvc da się ulepić. Rozproszenie ma znaczenie z powodu technicznego: niemożliwość założenie transakcji na kilka agregatów, co niestety wpłynie na biznes, bo fizyka, i trzeba to uwzględnić w procesie biznesowym, np. kompensując, czyli wycofując już poczynione kroki.

    1. Jarosław Żeliński

      ” trzeba dodać nowe klocki do tego mvc”
      owszem, bo MVC to jedynie podział na najwyższym poziomie

      “niemożliwość założenie transakcji na kilka agregatów”
      nie ma takiej potrzeby gdy nie ma współdzielonej bazy pod spodem

      “kompensacja”
      jak wyżej

      Bo generalnie to co widzę w wielu projektach, to najpierw z niewiadomego powodu powstaje jedna relacyjna baza danych, a potem cały zespół dzielnie walczy z jej wadami…

  6. Sławek Sobótka

    Nie to, że nie ma potrzeby przy kilku bazach danych. Nie ma takiej fizycznej możliwości (zakładam, że odrzucamy transakcje rozproszone). No i tu zaczynają się schody. Co jeżeli zapis do jednej bazy się powiedzie a do innej już nie?

    1. Jarosław Żeliński

      “Nie ma takiej fizycznej możliwości (zakładam, że odrzucamy transakcje rozproszone). No i tu zaczynają się schody. Co jeżeli zapis do jednej bazy się powiedzie a do innej już nie?”

      Jeżeli z góry zakładasz model relacyjny, setki połączonych tabel i kilometrowe Selecty to tak. Ale są inne możliwości, które tego nie wymagają. Bo Ty z góry zakładasz architektura pokazaną w powyższym tekście jak “rozsamrowaną”.

  7. Sławek Sobótka

    Nic z tych rzeczy, niech będą dwie bazy dokumentowe.

    1. Jarosław Żeliński

      Dokumentowy to jest system przechowywania danych a nie “bazy jako takie”. W dokumentowym systemie zachowanie nawet bardzo skomplikowanej faktury nie jest żadnym “selektem z setka tablic” tylko prostym zachowaniem jednego stringu w jednym kroku do jednego atrybutu. Dlatego tu nie występują żadne problemu o jakich piszesz. Zapis także jest jednym krokiem, więc nie ma potrzeby żadnej kompensacji. Jeżeli tych faktur jest sto milionów, to i tak każda na swoje ID i możesz je bez problemu zapisać na dowolnej liczbie osobnych serwerów i nie ma z tym, żadnego problemu.

      Te stringi nie nie niosą też żadnej logiki, więc nie ma tu żadnej kłopotliwej “płytkiej i głębokiej logiki”.

Możliwość dodawania komentarzy nie jest dostępna.