Rozdíl mezi Facebook FLUX a MVC rubrika: Programování: JavaScript

2 Josefff
položil/-a 14.4.2015

Zdravím, může mi někdo prosím vysvětlit, jaký je rozdíl mezi FLUX a MVC, všude se o tom mluví, všude se o tom píše, ale mě stále nedochází podstata. Mě se zkrátka zdá, že se jen zaměnil Controller za Dispatcher a Model za Store. Všechny ty nákresy vypadají podle mého úplně stejně, jen jsou tam prohozené ty názvy co jsem zmiňoval. Může mi někdo osvětlit v čem je rozdíl kromě jiných názvů? Díky

odkaz
5 VojtechMiksu
odpověděl/-a 16.4.2015
 
upravil/-a 17.4.2015

Dispatcher
Je v aplikaci jen jeden (singleton) a je to vcelku hloupoučký kus kódu (má celkem asi 60 řádků). Všechny stores si pak u něj zaregistrují svůj callback, což znamená asi toto: "Hej, dispatchere jsem tu! Pokud ti někdo pošle nějakou action (prachobyčejný event s typem a datama), dej mi vědět. Já se na tu action mrknu a pokud se mně týká, tak na ní nějak zareaguju." Proč vlastně dispatcher v aplikaci potřebujeme a nemůžeme actions posílat přímo do stores? Občas totiž vznikají mezi stores závislosti. Například store A musí zpracovat action 1 dříve než jí zpracuje store B. Stává se to především u větších aplikací. Dispatcher pak nabízí metodu waitFor. Stores jí můžou použít proto, aby zadefinovali svojí závislost na jiném store, aneb "Hele, musim počkat až si tuhle action zpracuje store B než začnu dělat něco já". Dispatcher pak dá vědět storum ve správném pořadí. Také kontroluje to, aby nevznikla kruhová závislost (store A čeká na B a B čeká na A).

Stores
Udržují stav aplikace. Jak komunikají s okolním světem? Zaregistrují si svůj callback u dispatcheru a čekají, co jim dispatcher pošle (viz výše). Většinou mají v sobě velký switch ve stylu: "Pokud mi dispatcher poslal action A, zpracuj její data funkcí X, pokud action B, zpracuj její data funkcí Y". Tyto zpracovávací funkce jsou většinou velmi triviální. Například to může být funkce addUser, která dostane objekt user a přidá ho do pole users[]. Po tom, co ho přidá do pole users, zakřičí: "Action je zpracovaná a změna je hotová" aneb vystřelí do světa event, že v daném storu došlo ke změně. Store pak poskytuje jednoduché get funkce - v našem případě getUsers(), která vrátí pole uživatelů. Klíčovou věcí je to, že store nemá žádné set metody. Views ani nikdo další tedy nemá žádný způsob, jak mu hrabat do jeho stavu. Pokud v něm chceme něco změnit, tak si musíme pěkně poslat action do dispatcheru.

Views
U Flux architektury to typicky bývá React. Kde React komponenta vezme data? Ve storu, protože nikde jinde žádné nejsou. A jak to udělá? No použije onu get metodu. Super. Ale jak pozná, že se data ve storu změnila a tudíž si má zavolat getUsers znova a provést překreslení? Jednoduše. Komponenta onen store poslouchá aneb pověsí na něj listener, který čeká na to, až nějaká zpracovávající funkce ze storu zakřičí, že něco zpracovala a změnila (viz výše, stores křičí, když v sobě něco změní). React ale není jen nějaký hloupý šablonovací systém. On je takovým mixem controller-view. Jeho hlavním posláním je namapovat data ze storů na DOM (a to myslím doslova, typicky v něm najdete spousty .map() funkcí ve stylu getUsers().map(user => <li>{user.name}</li>). Takhle by nám třeba vypsal uživatelská jména do seznamu. Je to ta část aplikace se kterou uživatel interaguje. Například tam bude input pro přidání dalšího uživatele. Paráda. Vyplní jméno a klikne na poslat. Co se teď stane? React bude mít na formuláři pověšenou svou metodu onSubmit. V ní především ihned pošle k šípku defaultní událost formuláře (tedy reload stránky). Je rok 2015, reloady nechme pro krále Klacka. Nu a zavolá nějakou prima funkci ... ehm, třeba addUser(...username z formuláře...). A to je zas co?

Action creators
Tohle je asi nejkomplikovanější a nezajímavější část aplikace. Je to velká sada stavu prostých funkcí typu výše zmíněného addUser(), které vytvářejí nové actions (eventy). Action dostane nějaké pěkné jméno jako USER_ADDED, aby jí mohli stores identifikovat a samozřejmě také data - uživatele. Zároveň si asi budeme chtít uživatele uložit i někam do MySQL databáze na serveru. Pošleme tedy nejdříve AJAX požadavek na server. Až dostaneme pozitivní odpověd, tak vezmeme onu připravenou action USER_ADDED a předhodíme jí dispatcheru (tkzv. ji dispatchne aka zavoláme Dispatcher.dispatch(action)). Nu a tady je pohádky konec, protože se nám uzavřelo jedno sexy jednosměrné kolečko.

Shrnutí
Základem Fluxu je onen jeden směr: Actions -> Dispatcher -> Stores -> Views (React komponenty) -> Actions. Dispatcher celému cirkusu šéfuje. Dostává z action creatorů actions a přeposílá je storům (někdy ve specifickém pořadí, pokud to potřebujeme). Ty je zpracují, pokud jsou pro ně zajímavé a vystřelí do světa zprávu, že se tak učinili a změnili v sobě svůj stav. React komponenty pak poslouchají story, které je zajímají a pokud se na nich něco změnilo, tak si getnou z nich data a znova se vyrendrují. React komponenty pak reagují na podněty uživatele a vytvářejí na jejich základě nové události, které pak zase přijdou pod ruku dispatcheru.

MVC uz popsal Obcan. Ze stylu otazky predpokladam, ze MVC uz asi znas, pouzivas a chces jen pochopit, proc se ted tolik mluvi o nejakem Fluxu. Navic popisu MVC je uz plny web (a to i ten cesky). Flux ti neušetří psaní. Je poměrně ukecaný. Věř ale, že se velmi vyplatí a i velká aplikace bude hračkou. Krásou je, že všechno se vším je tak nějak synchronizované, bez jakékoliv námahy. Přidáš někam do menu novou kategorii a ona se okamžitě objeví třeba i v hlavním sloupci webu, kde jsou kategorie opět vypsané v jiné formě.

Tohle je takový základ. Je skvěle použitelný. Dá se ale ještě dále cizelovat. Například stavy nemusíme ukládat přímo ve storech, ale můžeme je mít někde na centrálním místě v podobě jedné velké immutable struktury a pak k této obludě přistupovat pomocí cursorů, což má další prima výhody. React komponenty pak není potřeba rerendrovat pokaždé, když nějaký store začne křičet, ale pouze pokud se v něm skutečně něco změnilo. Rerender se sice pořád dělá jen nad virtuálním DOMem a tudíž šlape jako hodinky. Nicméně s pár úpravama se dají omezit právě i tyhle rerendery a pak to lítá tak, že se z toho štěstím rozbrečíš. Nicméně začít se musí postupně a optimalizovat klidně pozdějí.

Komentáře

  • rmaslo : Aha, snad jsem to už pochopil. Takže místo Controleru mám Action creators, který udělá to samý co Controler (tj. zapíše do db) a pak ještě aktualizuje Stores. A Stores jsou vpodstatě takový cache, který používá View aby nemuselo pokaždé šahat až do modelu. A protože Stores i Action creators je hodně a nechci řešit komunikaci m:n tak se to posílá přes jeden Dispatcher, abych měl jenom m:1 (kde m jsou ty akce z Action creators) a dispacher pak udělá 1:n (kde n jsou ty Stores). Takže ve View ušeřím dotaz do modelu. OK. To, že ušetřím dotaz do modelu pokud View jede na serveru nemá asi žádný velký význam, ale pokud mi View jede v prohlížeči (SPA) tak určitě néjaký čas ušetří. 16.4.2015
  • rmaslo : Vidím tam jeden velký problém. Jak dostanu logiku modelu zpět do stores? Tahle logika přeci může ukládaný záznam zcela překopat. Představme si, že v Modelu mám tuto třeba tuto primitivní logiku: Ukládanému uživateli změním velikost písmen tak, aby první bylo velké a ostatní malá. Určitě nechci programovat tu logiku modelu 2x - může být přeci i mnohem složitější a závislá třeba na jiných datech serveru. Určitě nechci zobrazovat něco jiného než se opravdu uložilo do modelu. Možná by šlo upravit API modelu tak, aby když ho volám AJAXem ve smyslu "přidej uživatele noVÁK" nevracelo jen OK či kód chyby (jak se to dělá normálně), ale v případě úspěchu i něco jako "přidal jsem uživatele Novák". Ovšem změna API Modelu je dost výrazný zásah do celého ekosystému aplikace ... na tom API mohou viset i jiní klienti... Jak se toto řeší? 16.4.2015
  • rmaslo : A to jsem ještě stále v jednom záznamu. Jak zaktualizuji třeba celkovou částku faktury po přidání jednoho řádku rozpisu faktury? V modelu mohu mít něco složitějšího třeba ve smyslu: Spočtu celkové ceny jednotlivých položek (tj. množství x cena + DPH a nějaký zaokrouhlení), pak to všechny řádky faktury sečtu (+ zaokrouhlení) a když je součet větší než x tak najdu položku doprava a její cenu dám na nula a znova to celý přepočtu. Fakt si moc nedovedu představit co by mi mělo lézt jako návratová hodnota toho AJAXu do modelu, abych podle toho poznal, že mám změnit cenu nějakého jiného řádku na 0. A tohle je běžný požadavek z praxe. 16.4.2015
  • VojtechMiksu : Doporucuji se uplne odprostit od uvah typu: "Stores jsou vlastne takovy model a tohle s timhle pripomina jakysi controller". Nefunguje to a budes se akorat trapit. Budu ted trochu abstraktnejsi: Jsou tam nejake dilci podobnosti, ale ten proces je z podstaty jiny. Predevsim je orientovany na data a udalosti. V moji Flux aplikaci prakticky nenajdes zadne tridy nebo dedeni. Vsechny casti jsou obycejne funkce, kterym na vstupu prijdou nejaka immutable data a oni je nejak pomoci map, reduce, filter transformuji a poslou dal. Takto to dotece az do cilove React komponenty (skrz strom nekolika dalsich React komponent), ktera uz vetsinou nic transformovat nemusi a zaverecnym map() je pouze vyrendruje. Ve funkcich nejsou zadne stavy (nejsou to objekty, instance neceho). Nic neblokuji. Pracuji, kdyz maji na vstupu data. Dale se snazis srovnavat server prostredi s frontendem. To jsou zase hrusky a jablka. Server prostredi je typicky bezstavove (z podstaty HTTP). Dostanes request, posles response a jede se dal. Je ti sumak, co se delo pred 2 minutama. Na frontendu ale stavy mas - uzivatel zacne vyplnovat formular a ejhle, mas hodnoty, se kteryma musis nejak nalozit. Do toho ti chodi AJAXem treba nejake hodnoty ze serveru, ktere s tim musis dat dohromady. A pak se uzivatel uplne splasi, zacne klikat kam nema a exploduje mu internet. Ty si ale se vsim musis nejak rozumne poradit a ten formular mu neodpalit. Vznika tedy nejaky sled souvisejicich udalosti. Nejsou to jen izolovane request-response dvojice. Frontend je v tomhle daleko tezsi. A ted konkretneji k druhe a treti otazce. Asi bys na ne dostal 10 odpovedi od 10 programatoru. To kam presne umistis jakou logiku (tu ci onu transformaci) je dost veci tveho citu a zkusenosti. Nekdy budes mit nejakou superslozitou operaci aka image processing, kterou musi pro tebe udelat server a tak si to posles pres API na nej. Pripadne potrebujes do vysledku zapocitat i 300MB dat v databazi, to asi take bez serveru nepujde. Pokud ale chces spocitat vysledek faktury napric radky, tak asi sam citis, ze posilat si to na server (a cekat na odpoved) by nebylo pro uzivatele uplne ono. Klidne to soupni do storu - bude to ve funkci, ktera ceka az ji Dispatcher posle action ITEM_ADDED. Pak nad polem ITEMS[] zavola nejaky sousled reduce, filter, map... behem toho ty polozky klidne i updatuje a pak jeste vysledek muze ulozit do dalsiho stavu INVOICE_RESULT. Pokud je to ale jen trocha scitani, tak neni ani potreba mit stav INVOICE_RESULT, ale proste to muzeme pokazde primo pocitat v getInvoiceResult() z ITEMS[] znova. No a pokud je to vylozene prkotina typu, do inputu ABC musi uzivatel vyplnit jen platne PSC, tak tu validaci klidne nech primo v React komponente. Action creators jsou typicky vhodne na to, aby komunikovali s externimi zdroji (API, localstorage, WebSQL). Maji tu jedinecnou moc, ze action (eventu) kterou poslou do sveta (dispatcheru) muzou zkonzumovat treba vsechny story v aplikaci. A i v nich uz delam nejake transformace. Napriklad API mi vrati 20 hodnot, ze kterych potrebuju jen 3 a dalsich 5 jsou nejakou kombinaci/upravou tech ostatnich. Vim, ze takhle ty data budu potrebovat vsude ve zbytku aplikace, tak si je upravuju uz primo tady - u pramene, nemusim to zanaset az nekam do storu. Zaverem. Neboj se dat do frontendu vsechno, co te napadne. Budes prekvapen, jak vykonny dnesni javascript je. Nejake pocitani faktur ho opravdu nerozhodi (znas urcite treba aplikaci Google Docs). JavaScript je brutalne rychly! Co mu vsak umi pristihnout kridelka je kolos zvany DOM (proto vzniknul ultimate fighter jmenem React, ktery si umi DOM pekne ochocit). O serveru zacni premyslet jako o pouhem ulozisti dat. Pokud pouzijes na serveru take javascript (node.js), muzes se pak uz uplne vyhnout tomu, abys mel nekdy na obou stranach stejnou logiku (typicky nejaka validace). Muzes ji dat do jednoho souboru a ten si importnout na obe strany. Tak jednoduche to je. No a zakladem je neco zkusit naprogramovat, protoze ctenim clanku se dostanes jen takhle daleko. 17.4.2015
  • the_ufon : Vojto, tleskám jak odpovědi, tak komentáři, líp už snad princip popsat nejde! Díky! 17.4.2015
  • rmaslo : Stále tu nefunguje ukončení odstavců tak to budu číslovat... 1. Díky moc za rozsáhlou odpověď. 2. Proti programům, které mají odděleně data a kód nic nemám. Takže to, že program není ze tříd, ale spíše z bezstavových funkcí a data jsou v jiném kontejneru mi spíše vyhovuje. 3. Události mám rád (radši než OOP). 4. Výkonnost JS považuji za zcela dostatečnou a nemám s ní žádný problém. 5. Chápu, že spočítat součet faktury na klientovi je mnohem rychlejší než se ptát serveru. 6. Udělat výpočet sumy faktury jen na klientovi a posílat to serveru považuji za hrubou bezpečnostní chybu. JS na klientovi není zabezpečený, kdokoliv do něj může hrábnout a uložit jako sumu faktury do db jako sumu faktury nějaký nesmyslný číslo si rozhodně nemůžu dovolit. 7. Z toho vyplývá, že výpočet sumy faktury musím mít 2x, jedenkrát na klientovi (kvůli rychlosti) a potom znovu na serveru (kvůli bezpečnosti). 8. Funkci pro ten výpočet rozhodně nechci psát 2x. 9. Takže musím mít na klientovi i na serveru "stejný jazyk". 10. OBECNĚ: Přemýšlet o serveru jen jako o úložišti dat a přesunout doménovou logiku jen na klienta podle mě nelze - je tam obrovské bezpečnostní riziko "zfalšování doménové logiky". 11. U složitějších aplikací kde doménové logika je něco víc než validace se podle mě bez JS na serveru neobejdu. (Možná by šla nějaká šílenost typu "doménová logika ve vlastním jazyce" interpretovaném na straně serveru třeba v PHP a na straně klienta v JS) 12. Kód tvořící doménovou logiku je veřejný, ale to snad na první pohled ničemu nevadí. 17.4.2015
  • VojtechMiksu : Ad priklad s fakturama: V tomhle pripade bych si posilal na server ty ITEMS[] a ne INVOICE_RESULT (k cemu by mi taky bylo nejake izolovane cislo, ze). Ad 6) Neni ani potreba hrabat do JS, staci uplne jednoduse posilat vlastni requesty do API. Vzdycky bude zalezet na tom, co delas. U te faktury je jasne, ze si na to musis dat bacha i na serveru, protoze by te to mohlo stat penize. Pokud si ale zmenou FE logiky muze uzivatel uskodit pouze sam sobe, tak neni potreba to moc resit (ono neni technicky rozdil v tom jestli si vsechny svoje data smaze pomoci formulare na strance, nebo manualne pomoci requestu do API). Tvym ukolem je, se hlavne postarat o to, abys nemel ve skriptech nejaka XSSka a ten request neposlal za uzivatele nekdo cizi. :-) 17.4.2015
  • rmaslo : jj souhlas. Je to celkově dost komplikovaný, ale je to asi jediná rozumná odpověď na to co dělat pokud chceme aplikaci zrychlit. A zajímavým způsobem to odpovídá na otázku o kterou se občas s kolegama přeme. Totiž kde je optimální ukládat doménovou logiku? Kolegové říkají v PHP objektech já říkám v db trigerech. A tento koncept říká v, že JS funkcích :-) a zdůvodňuje to tím, aby šla použít na serveru i na klientovi. Ještě jednou dík za osvětu. 17.4.2015

Pro zobrazení všech 2 odpovědí se prosím přihlaste:

Rychlé přihlášení přes sociální sítě:

Nebo se přihlaste jménem a heslem:

Zadejte prosím svou e-mailovou adresu.
Zadejte své heslo.