Mi van az MVC tervezési mintán túl?

By | 2012. március 18.

A webfejlesztők körében igen elterjedt az MVC tervezési minta, ma már talán nincs is olyan web alkalmazás keretrendszer, amely ne támogatná. De mitől lett ilyen népszerű? Valóban megoldást jelent minden problémára? Ha nem, akkor merre induljunk el? Ezekre a kérdésekre keresem a választ a cikkemben.

Az MVC sokkal régebbi, mint gondolnánk, már jóval a világháló megalkotása előtt létezett. A webes világba viszont csak 2004-ben, a Ruby on Rails keretrendszer hatására került be, amely demonstrálta a minta előnyeit. Ennek hatására gombamód kezdtek el szaporodni a Railsre hajazó, MVC mintára épülő keretrendszerek, köztük a KSZK Webteam által is használt Symfony keretrendszer (a korai változataiban még sok osztály és metódus neve is megegyezett a Railsben használatosakkal). A Microsoft viszonylag későn lépett be erre a piacra, csak 2009-ban jelent meg az ASP.NET MVC első verziója, azóta viszont nagyon dinamikusan fejlődik, mi a Cukorka weboldalhoz már a 3-as verziót használjuk.

De mitől lett ilyen népszerű? Szerintem alapvetően a következő okai vannak:

  • A web alkalmazások alapvetően HTTP kérések és válaszok sorozatára épül, minden egyes kérés-válasz egy-egy kör, melyben először a felhasználó csinál valamit, ennek hatására az alkalmazás módosítja az adat modellt, majd visszaküldi a módosított nézetet. Ehhez a ciklushoz nagyon szépen illeszkedik az MVC minta.
  • A web alkalmazások szükségszerűen különböző technológiák kombinációiból épülnek fel, tipikusan van egy adatbázis, HTML kódok és a futtatható kód. Ennek megfelelően „magától” bomlik rétegekre, melyre természetes módon illeszkedik az MVC minta.

Az MVC ugyanakkor nem egy adott megoldás, csak egy minta, amely viszonylag laza utasításokat ad a fejlesztőnek. Emiatt számos olyan kérdés van, amire nem ad konkrét választ, a következőkben ezekből említek egy párat:

  • Validáció, azaz a felhasználótól érkező adatok hitelesítése hova kerül? Ha a legalapvetőbb objektum-orientált elvet, az egységbezárást veszem alapul, akkor a Model osztályokba kell helyezni. Ugyanakkor az is teljesen logikus érv, hogy a felhasználótól érkező adatok az űrlap részei, melyet a beérkező kéréssel a Controllernek kell kezelnie, így a hitelesítés is ide tartozik. Implementációtól függ.
  • Mennyire legyen okos a View? Azaz mennyi futtatható programkódot tartalmazhat? Ha szigorúan veszem, akkor HTML kódon kívül semmi más nem lehet benne, ilyen értelemben egy if elágazás, vagy egy foreach ciklus sem. Ennek ellenére gyakran látunk ilyeneket, egyszerűen azért, mert ezek elkerülése túl sok plusz munkát jelentene ahhoz képest, amennyit nyerünk vele. Másik lehetséges út az okosabb View irányába az adatkötés, ez szinte alapvetőnek számít a WPF és Silverlight alkalmazások esetén, de ott a megjelenítő réteg nem a HTML, hanem a sokkal erőteljesebb XAML. Általában a webes világban inkább törekedjünk arra, hogy minél butább legyen a View.
  • Aktív vagy passzív legyen a modellem? Aktív modellről beszélünk, ha a modell is belehív a Controller metódusaiba, vagy értesítést küld a View-nak a változásokról. Ez utóbbi is inkább a vastagkliens alkalmazásokra jellemző, webes alkalmazások esetén kevésbé van értelme, éppen a korábban említett kérés-válasz jelleg miatt.

A végére hagytam a legérdekesebb részt: hova tartozik az adatrögzítés és lekérdezés, azaz adat elérési logika? Beszéljünk egész konkrétan, ha relációs adatbázist használunk, hova kerülnek az SQL kérések (vagy LINQ kérések .NET esetén, vagy HQL kérések Hibernate esetén)? A modell magát az adatot reprezentálja, és nem a tárolást. Egy modell objektum egy-egy sornak felel meg az adattáblában, egy SELECT SQL kéréssel pedig tipikusan több sort is lekérhetünk, amelyből majd egy modell objektumokból álló tömböt vagy listát szeretnénk létrehozni, tehát a modellbe semmiképpen sem fér bele. A Controllerre azt mondjuk, hogy kezeli a felhasználói kéréseket, kiválasztja a View-t, és ha kell, frissíti a modellt. Szó sincs adat lekérdezésről, vagy modell példányosításról, mégis nagyon gyakran ide kerül az említett kód. Én is sokszor teszek pl. LINQ kéréseket a Controllerbe, és kisebb alkalmazások esetén ez teljesen rendben is van.

Sajnos tapasztalatból mondom, hogy ha a fentieknek megfelelően a Controllerbe kerül a validáció, a lekérdezések és a modell példányosítás is, akkor már egy közepes méretű alkalmazás esetén is nagyon csúnyán meg tud hízni Controller, ami azért probléma, mert sokkal nehezebb lesz karbantartani. Ezáltal a Controller egyfajta Isten osztály lesz, arról pedig tudjuk, hogy kerülendő.

Másik lehetséges megoldás, hogy a Model osztályokba teszünk statikus metódusokat (pl. GetAll() ), amelyeken keresztül történik a lekérdezés. Szerintem ez nagyon ronda megoldás, ahogy fentebb is írtam, a modell osztály egy sort reprezentál és nem az  adattáblát. A modellnek egyébként is érdemes függetlennek lennie a tárolási eljárástól a hordozhatóság és tesztelhetőség érdekében.

Szerintem a fentiek alapján már világos, amit mondani szeretnék, hogy az MVC mintába nem nagyon fér logikailag sehova az adat tárolása, lekérdezése. A legszebb megoldás egy újabb réteg bevezetése a három mellé. Egyik lehetséges megoldás a Repository pattern alkalmazása. A Repository feladata az adatok lekérése, tárolása, a modell példányok létrehozása. Ezáltal megvalósul az üzleti logika és az adatelérés függetlensége.

Összefoglalásként elmondható, hogy az MVC minta kiválóan illeszkedik a webes világhoz, viszont hátránya, hogy nagyon általános, az egyes implementációk merőben eltérhetnek egymástól. Ezen kívül láttuk, hogy akad olyan terület is, ami kívül esik az MVC hatáskörén, így nem szabad csak három lyukban gondolkozni, melyek közül egyikbe mindenképp bele kell erőszakolni a kódunkat, elég nagy alkalmazás esetén nyugodtan gondolkozhatunk háromnál több rétegben.

Nektek milyen tapasztalataitok vannak ezzel kapcsolatban?

  • jtakacs

    A probléma szerintem nem azzal van, hogy az M, V, C betűk közül melyikbe pakoljuk a kód nagy részét, hanem azzal, hogy mindenki az egész alkalmazásra próbálja meg ráhúzni az MVC mintát.
    Ha rétegenként vizsgálod a problémát, akkor első körben lebontod az alkalmazásodat interface-ekre, minden réteg csak ezeken az interface-eken keresztül adhat át adatot a következő rétegnek akkor máris világos, hogy hol a helye az MVC-nek: minden rétegben, külön külön megvalósítva.
    SQL-ben a model a tárolt adat, a view az ezeket lekérdező select-ek, a controller pedig az összes többi sql statement, amivel módosítani tudod a modell állapotát. A következő réteg az alkalmazás, itt a modell az entitás objektumok összessége, a controller az ezeken műveleteket végző objektumok halmaza, a view pedig lehet sokminden, attól függően, hogy milyen ui fogja megjeleníteni az adatokat. Legyen az ui mondjuk egy egyszerű ajaxos webalkalmazás. Ekkor a view kis része html-t fog generálni, a nagyobbik része meg sokféle json objektumot. A webalkalmazás kliensoldali részén a modell a DOM, controller minden, ami a felhasználói eseményekre reagál, a view pedig minden, ami a megjelenítést szolgálja (html + css + javascript kód).
    Az meg szerintem evidens, hogy a view-be is kell kódot tenni a legtöbb esetben. Na persze nem ide kell egy ezersoros táblázatot lekérdező modul kódját betuszkolni. Minden view az összes szükséges adatot adja át a következő rétegnek, de a lehető legminimálisabb formában. Ha van egy json-t generáló view, akkor erre már egyszerű ráhúzni szerveroldalon egy másik view-t, ami ezt feldolgozza, és statikus html táblát gyárt belőle. így az ajaxos kliens is tudja használni az applikációt, meg megfelelő átirányítás után az a júzer is, aki kikapcsolta a javascriptet a böngészőben.

  • szelpe

    Köszi a hozzászólást, elgondolkodtató, amit írtál! 🙂