Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Diskuze: Filozofie Nette modelů - proč to není špatně?

Aktivity
Avatar
Petr Čech
Tvůrce
Avatar
Petr Čech:8.4.2018 12:00

Ahoj, nemám ani tak otázku, spíš bych chtěl otevřít diskuzi a zjistit názory ostatních.
Takže k věci. Už jsem dělal na pár projektech v Nette a v žádném kromě těch mých se mi vůbec nelíbilo, jak jsou udělané modely. Většinou je to totiž stylem asi takovýmto:

// BaseModel má jen DI pro DB Context
class UserModel extends BaseModel
{
    public function listUsers()
    {
        return  $this->database->table('user')->order('surname ASC')->fetchAll();
    }

    public function getUser($id)
    {
        $res = $this->database->table('user')->where(['id' => $id])->fetch();
        if (!$res) throw new NoDataFound();
        return $res;
    }

    public function insertUser($values)
    {
        $date = new DateTime();
        $row = $this->database->table('user')->insert([
              'surname' => $values['surname']
            , 'firstname' => $values['firstname']
            , 'phone' => $values['phone']
            , 'registered' => $date
            , 'is_admin' => false
        ]);
        return $row->id;
    }
}

Tohle je třeba kód poctivě ukradený z předmětu, co se zabývá specificky Nette na FIT ČVUT, tak bych předpokládal, že to bude používat nějaké best practices.
Dále se tedy pracuje s tím, co tohle vrátí, tedy nějaké Selection/IRow, neexistuje třeba nějaká UserEntity. Políčka formulářů potom přímo odpovídají názvům sloupců z DB.
Proč tohle není cesta do pekel? Teď když zjistím, že jsem udělal překlep ve jménu sloupce db, nebo chci třeba udělat nějakou vlastnost virtuální, můžu to celé přepsat nějakým globálním search & replace.
Já bych třeba očekával, že se to bude modelovat tak, že bude:

  • Entita - reprezentuje samotná data a je to třída, takže to mohu rozumně rozšiřovat. Pravděpodobně bude mít i update() a delete(), ale ne tak, že bude brát pole, postaví si to z vlastností
  • Collection/Manager - bude obstarávat něco jako get($id), fetchAll()
  • Ve všem kromě vnitřností entity a někdy Manageru už jsem nezávislý na databázi a sahám jen na vlastnosti entity

A já se tedy ptám: proč tohle není cesta do pekel?

Odpovědět
8.4.2018 12:00
the cake is a lie
Avatar
David Hartinger
Vlastník
Avatar
Odpovídá na Petr Čech
David Hartinger:8.4.2018 12:59

V tvé otázce mi nějak chybí otázka. Co že je na tom podle tebe konkrétně špatně?

Nette nemá ORM, proto tam není User entita. Můžeš si zkusit tu entitu udělat a data na ní namapovat. Zjistíš, že to není tak jednoduché a že napsat ORM framework je dost práce. Proto se to bez něj tak nedělá.

Kdyby tam entita byla, neměla by určitě mít metody update() ani delete(), protože Active Record je antipattern. Entita by neměla mít odpovědnost za své odstranění z nějakého kontextu, ale ten kontext by ji měl odstraňovat. Proto by update/delete metody byly v nějaké User service.

Nahoru Odpovědět
8.4.2018 12:59
New kid back on the block with a R.I.P
Avatar
Martin Dráb
Tvůrce
Avatar
Odpovídá na Petr Čech
Martin Dráb:8.4.2018 13:28

Tohle je třeba kód poctivě ukradený z předmětu, co se zabývá specificky Nette na FIT ČVUT, tak bych předpokládal, že to bude používat nějaké best practices.

To podle mě záleží na tom, na jak velký projekt se to použije. Jestli se ti vyplatí nějak obalovat to, co ti ten model vrací, do entity, nebo by to znamenalo zbytečný čas navíc.

Jasně, můžeš později zjistit, že bys třeba některou vlastnost potřeboval udělat virtuální... a máš problém. Na druhou stranu, když se takový požadavek později vynoří, znamená to, že se jedná o něco, s čím jsi v době zadání nepočítal a převedením vlastnosti do virtuální podoby jen oddaluješ nevyhnutelné (tohle samozřejmě hodně záleží na okolnostech).

Co se týče toho, že ti to vrací třeba nějaké pole s klíči odpovídajícími názvům sloupců v DB tabulce a případné změny názvů těchto sloupců – tento případ pořád ještě lze umlátit zavedením konstant na ty názvy. Pak teoreticky stačí jen měnit hodnoty těch konstant a nemusíš dělat nějaké šílené globální find & replace.

Vůbec bych neměl problém uvádět takový kód na tom předmětu jako "správný", když se vysvětlí, jaké má potenciální nedostatky. A bude se třeba pokračovat na tu entitu. Vždycky je potřeba najít kompromis mezi mírou abstrakce (= možnost dobře reagovat na změnu požadavků) a třeba čitelnosti/přeh­lednosti, výkonu.

Nahoru Odpovědět
8.4.2018 13:28
2 + 2 = 5 for extremely large values of 2
Avatar
Petr Čech
Tvůrce
Avatar
Odpovídá na David Hartinger
Petr Čech:8.4.2018 13:32

Dobře, ale jak v tomto uděláš refactoring? Prostě nemáš na výběr nic lepšího než global search &replace.
Není přeci třeba nějaký bloated ORM framework. Mně se o veškeré ORM stará dohromady as 550 řádků + konkrétní implementace (2 primitivní metody a mapu atributů), takže overhead je minimální.
ActiveRecord je antipattern, takže budeme vracet ActiveRow a Selection?
Abych odpověděl na otázku, co je špatně: Pokud něco změním, musím to hledat v:

  1. model, všechny stringové klíče pole
  2. formuláře
  3. šablony

To si koleduje o strašně moc chyb. Když místo toho budu mít na jednom místě stringy a potom se na ně buď budu odkazovat, nebo budu používat vlastnosti entity (a důsledně budu dělat typehinty), dokážu velice snadno něco měnit, protože např. IDE mi dokáže jednoznačně najít všechny použití této konkrétní vlastnosti a jediné, kde to musím najít ručně jsou šablony.
Když všude budu mít pole se stringovými klíči, nepůjde to měnit nebo dokonce můžu udělat překlep a jediné místo, kdy zjistím, že to je špatně je, až se to dostane k databázi a ta řekne, že nezná sloupeček.

Nahoru Odpovědět
8.4.2018 13:32
the cake is a lie
Avatar
Petr Čech
Tvůrce
Avatar
Odpovídá na Martin Dráb
Petr Čech:8.4.2018 13:42

Jasně, můžeš později zjistit, že bys třeba některou vlastnost potřeboval udělat virtuální... a máš problém. Na druhou stranu, když se takový požadavek později vynoří, znamená to, že se jedná o něco, s čím jsi v době zadání nepočítal a převedením vlastnosti do virtuální podoby jen oddaluješ nevyhnutelné (tohle samozřejmě hodně záleží na okolnostech).

Mám docela dost velký problém, protože buď musím přepsat prakticky všechny modely, nebo si na jednom modelu zavedu mezivrstvu a budu všude dělat výjimky - hodně štěstí s tím.

Přitom kdybych měl od začátku mezivrstvu - třeba jen wrapper ArrayHash, co by tahal vše přes __get, __set, půjde to relativně snadno...

Co se týče toho, že ti to vrací třeba nějaké pole s klíči odpovídajícími názvům sloupců v DB tabulce a případné změny názvů těchto sloupců – tento případ pořád ještě lze umlátit zavedením konstant na ty názvy. Pak teoreticky stačí jen měnit hodnoty těch konstant a nemusíš dělat nějaké šílené globální find & replace.

Já to třeba používám, ale proč to nepoužívá nikdo jiný (resp. neviděl jsem to nikde jinde)? To je přeci v podstatě kopírování kódu - tedy skoro jako opisovat všude ten samý kód místo vytvoření metody. Když chceš potom tohle předělat, musíš to udělat úplně stejně jako kdybys všude kopíroval kód místo metody.
Přesto se zdá, že to nikoho nevzrušuje...

Nahoru Odpovědět
8.4.2018 13:42
the cake is a lie
Avatar
David Hartinger
Vlastník
Avatar
Odpovídá na Petr Čech
David Hartinger:8.4.2018 13:57

A těch 550 řádků ti jako i mapuje joiny a optimalizuje je? To bych potom docela rád viděl. Migrace třeba je pak kapitola sama pro sebe.

Stringy si samozřejmě koledují o chyby, ale ORM je prostě složité. Proto ho Nette nemá, proč myslíš, že tam není? :) A na ČVUT bys očekával co? Že si nejdříve napíší vlastní ORM a pak udělají ty příklady v tom nebo jak? Bez ORM prostě entity nemají smysl a Nette ORM nemá. Snad jsem ti odpověděl.

Nahoru Odpovědět
8.4.2018 13:57
New kid back on the block with a R.I.P
Avatar
Petr Čech
Tvůrce
Avatar
Odpovídá na David Hartinger
Petr Čech:8.4.2018 14:10

Jasně, že nedělá, ORM je pro to dost honosný název :D. mapuje to jednoduché modely na ty pole, o optimalizace se stará Nette database.

Nahoru Odpovědět
8.4.2018 14:10
the cake is a lie
Avatar
David Hartinger
Vlastník
Avatar
Odpovídá na Petr Čech
David Hartinger:8.4.2018 14:17

A když uděláš dotaz třeba na články a jejich autory (SELECT title, description, first_name, last_name FROM article JOIN user USING (user_id)), tak ti to potom namapuje na co?

Nahoru Odpovědět
8.4.2018 14:17
New kid back on the block with a R.I.P
Avatar
Petr Čech
Tvůrce
Avatar
Odpovídá na David Hartinger
Petr Čech:8.4.2018 14:27

Implicitně se to neumí namapovat na nic, ale pomocí polymorfismu se dá tohle manuálně přepsat na cokoliv, co chceš.

Nahoru Odpovědět
8.4.2018 14:27
the cake is a lie
Avatar
Petr Čech
Tvůrce
Avatar
Odpovídá na David Hartinger
Petr Čech:8.4.2018 14:30

Tím se ale vzdalujeme o samotného tématu, má ORM třída je celkem irelevantní, já jsem chtěl vědět, proč se to dělá tak, jak se to dělá.

Nahoru Odpovědět
8.4.2018 14:30
the cake is a lie
Avatar
David Hartinger
Vlastník
Avatar
Odpovídá na Petr Čech
David Hartinger:8.4.2018 14:33

Nevzdalujeme, psal jsi, že stačí napsat 550 řádků, tak se snažím dopátrat toho, zda je to pravda nebo ne. Pokud ne, tak nemá smysl dále diskutovat a můžeme se spokojit s tím, že ORM FW si prostě sám nenapíšeš. Pokud ano, řekni mi jak bys vypsal takový seznam článků s jejich autory s tím tvým FW?

Nahoru Odpovědět
8.4.2018 14:33
New kid back on the block with a R.I.P
Avatar
Petr Čech
Tvůrce
Avatar
Odpovídá na David Hartinger
Petr Čech:8.4.2018 14:55

Asi takto:

class Article extends Entity
{
        /**
         * @var User
         */
        public $author;

        /**
         * Return class which serves as a map between db and model
         * @return string
         */
        protected function getMapClass()
        {
                return TArticles::class;
        }

        /**
         * Returns the name of the table related
         * @return string
         */
        public function getTableName()
        {
                return 'article';
        }

        protected function importSpecial(IRow $row)
        {
                $author = new User;
                $user->importFromDb($row[TUsers::author]);
                $this->author = $user;
        }
}

class TArticles {
        const id = 'id';
}

Neříkám, že to stačí pro všechno, ale je to takový základ, co si může napsat každý a potom rozšiřovat. Asi ne na úlohách ze cvičení, ale třeba masivní projekty by něco takového použít určitě mohly... ale nepoužívají (co jsem měl možnost vidět)

Editováno 8.4.2018 14:58
Nahoru Odpovědět
8.4.2018 14:55
the cake is a lie
Avatar
David Hartinger
Vlastník
Avatar
Odpovídá na Petr Čech
David Hartinger:8.4.2018 15:22

A ten dotaz by vypadal jak?

Nahoru Odpovědět
8.4.2018 15:22
New kid back on the block with a R.I.P
Avatar
Petr Čech
Tvůrce
Avatar
Petr Čech:10.4.2018 19:17

Měl jsem v plánu to napsat, ale začal jsem si procházet tu mou implementaci a zjistil jsem, že jsem to psal v době, kdy jsem neznal lazyloading... takže abych to provedl, nebylo by to vůbec efektivní.
A když se zamyslím nad tím, jak se to musí udělat tak, aby to bylo hezké, vlastně se to musí udělat jako takový fancy obal pro to, jak se to dělá, takže je vlastně všechno jasné :D Je tedy škoda, že nemá Nette vestavěné ORM, ale aby se to dalo udělat dostatečně obecně, byla by to strašlivá obluda...

Akceptované řešení
+5 Zkušeností
Řešení problému
Nahoru Odpovědět
10.4.2018 19:17
the cake is a lie
Avatar
David Hartinger
Vlastník
Avatar
Odpovídá na Petr Čech
David Hartinger:10.4.2018 19:24

Mám dojem, že chodíš na srazy, někdy bych se na to stejně rád podíval, jestli se tam potkáme :)

Nahoru Odpovědět
10.4.2018 19:24
New kid back on the block with a R.I.P
Avatar
Petr Čech
Tvůrce
Avatar
Odpovídá na David Hartinger
Petr Čech:10.4.2018 21:43

Já to nejspíš jednou přepíšu pořádně a někam ten kód hodím jako knihovnu.

Nahoru Odpovědět
10.4.2018 21:43
the cake is a lie
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 16 zpráv z 16.