Dependency injection si razí cestu do 2
- David Grudl
- Nette Core | 8227
Jedna z klíčových věcí v jedničce bude návrat ke kořenům, k dependency injection. Používání této techniky z frameworku spíš časem vyprchalo a dnes programátor spíš šáhne po statické třídě Environment, než aby využil kontejneru ServiceLocator. Chtěl bych to změnit.
Jednak samotný kód frameworku nebude nikde třídu Environment využívat a striktně přejde na objekt service locatoru. Samotný lokátor chci zjednodušit a lépe zasadit do frameworku. Docela uvažuju nad tím, že bych ho přejmenovat, přeci jenom dlouhý název je taky odrazující ;-) (koneckonců právě proto se Environment jmenuje Environment).
Nechci použít v DI užívaný název container, protože ve frameworku už existují kontejnery s jiným významem. Tuším se používá i název provider, ten je zatím „volný“. Podobně je na tom název context. Čemu byste dali přednost?
(pokud nevíte, o čem je řeč, brzy vysvětlím)
- Patrik Votoček
- Člen | 2221
WOW cool. No mě se to zkracování moc nelíbí. ServiceLocator mě nepřipadá dlouhé. Ale kdybych se musel rozhodnout asi bych volil provider.
- Ondřej Mirtes
- Člen | 1536
Context je moc pěkný název a přesně vyjadřuje jeho podstatu :) Taky jsme si s ním v Mediu dali práci :o)
- Dragon Jake
- Člen | 20
rarous napsal(a):
Manager je ukázkový případ zoufalosti, kdy netušíš, jak to pojmenovat. Vždycky existuje výstižnější název. ;)
aneb jak je slovo manage v angličtině univerzální, pravda :) provider se mi líbí dostatečně.
- rarous
- Člen | 59
BTW ServiceLocator není DI. Dokonce je to DI antipattern. Něco takového zavádět není moc dobrý nápad. V DI se používají názvy Container, Kernel nebo ObjectFactory. Pokud nechcete použít Container, který je IMO nejsprávnější, použijte Kernel. Jiné názvosloví bych rozhodně nezaváděl.
- David Grudl
- Nette Core | 8227
Vyjádřil jsem se docela nešťastně, nechci přejmenovávat Service Locator kvůli délce, to je podřadný důvod se smajlíkem. Spíš si nejsem jist jeho vhodností. Jednak je to zaběhlý pojem pro návrhový vzor, o jehož implementaci se nesnažím a locator v Nette nemusí nijak korespondovat se Service Locatorem jinde. Provider nebo Context asi blíže odpovídají záměru, který hned vysvětlím. U termínu Provider mi vadí jen to, že automaticky následuje otázka „poskytovatel čeho?“. Context si pamatuju z Mojavi blahé paměti a připomněl mi ho Ondra s Honzou Tichým.
Popíšu současný stav: Pokud chci v bootstrapu použít Application, kdekoliv přistoupit k HttpRequest nebo k úložišti cache, nevytvářím si instanci třídy sám, ale požádám o ni ServiceLocator. Což je vlastně chytřejší registr objektů, který umí kromě samotných objektů registrovat i callbacky na továrničky, viz dále. Přičemž Nette vytváří i defaultní nebo chcete-li iniciální lokátor (třída Configurator), který je dostupný skrze třídu Environment.
Co je vlastně smyslem locatoru? Řeší úskalí dependency injection. Totiž, pokud chci v nějaké komponentě pracovat s aktuálně přihlášeným uživatelem, mám dvě možnosti, jak to udělat:
- předám ji přes konstruktor nebo setter objekt User (DI)
- nepředám ji nic a komponenta si „magicky“ šáhne na Environment::getUser() (to nechceme)
S bodem 1) souvisí jeden vážný a jeden méně vážný problém. Abychom mohli objekt User předat, musí existovat jeho instance. Ale co v případě, že vytvoření objektu User je drahé a komponenta ho potřebuje jen občas? Hodil by se nám nějaký lazy přístup. Druhý problém je ten, že kdejaká komponenta používá celou řadu objektů-služeb a počet setterů nebo parametrů v konstruktoru by neúměrně rostl.
Řešením je právě zmíněný service locator – předám jeden objekt a ten nese balík všech dalších objektů. Navíc má schopnost registrovat i továrničky, takže při prvním požádání o např. HttpRequest se zavolá továrnička a ta objekt sestaví a locator jej nadále vrací. Samotný volající objekt přitom nepozná rozdíl. A oba uvedené problémy se tím vyřeší.
Takhle funguje Nette snad odjakživa a zdá se mi to vcelku OK. Pojďme na to, co OK není.
Jednak dnes se využívá prakticky jen ten iniciální lokátor a to navíc
přes globální volání. Namísto toho, aby se do jednotlivých komponent
propagoval vlastní lokátor. Chybu vidím v tom, že používání vlastního
lokátoru bylo vždycky méně cool, než napsat
Environment::getUser()
. I přes ten krkolomný název ;-) A dále
jsou tu úzká místa Nette, kupříkladu třídě Application nastavit vlastní
lokátor jde blbě, k tomu určená $defaultServices
není
dostatečná cesta.
Řešením je samozřejmě přidat do Application & spol. metodu
setServiceLocator (nebo využít run(), jak navrhovali Mediáci), ale také
předělat API samotného locatoru. Přeci jen
$this->locator->getService('Nette\Web\IUser')
těžko trumfne
Environment::getUser()
.
- Filip Procházka
- Moderator | 4668
co takhle naučit ServiceLocator aliasy jako to umí Environment
$locator->setAlias('user', "Nette\\Web\\IUser");
// potom kdekoliv v komponentách/application... apod
$this->locator->getUser();
Editoval HosipLan (16. 9. 2010 14:54)
- Patrik Votoček
- Člen | 2221
Nevím proč mě připadá že na konci chybí odstavec:
Proto v 1.0 bude lokátor řešen
$this->foo
… Jen přijít na ten vhodný název pro foo.
To výše je jenom příklad.
Nebo jsem úplně mimo?
- Blizzy
- Člen | 149
Ve frameworku se vyskytuje pouze ComponentContainer, podle mě by se ten pozměněný ServiceLocator měl jmenovat ServiceContainer. Zmatení by to snad nezpůsobilo, komponenty a služby jsou diametrálně odlišné věci.
Název ServiceLocator ve mně evokuje, že jde o jeden objekt, singleton. Název ServiceContainer mi připadá spíše vhodný pokud má být objekt předáván, zdá se mi to lepší název, pokud těchto objektů bude více.
Editoval Blizzy (16. 9. 2010 21:43)
- jantichy
- Člen | 24
Za sebe samozřejme hlasuju pro Context, protože to vystihuje podstatu a smysl užití mnohem lépe, než Provider.
Provider, stejně jako ServiceLocator tak trošku zavání tím, že je to nějaký „globální“ autoritativní poskytovatel napříč celou aplikací. Jakože když chci identitu uživatele nebo doctrine entityManager, tak že je to stejná hodnota napříč celou aplikací a dostanu z toho to samé, ať se zeptám, kde se zeptám.
Naproti tomu Context vyjadřuje celou podstatu věci, totiž že na každém jednom dílčím místě aplikace to může obsahovat úplně jiné věci, podle toho, jak se zrovna volající nadřazený kód rozhodl a jak to potřebuje.
Takže moje +1 pro Context.
- jantichy
- Člen | 24
A čeho je to kontext, smím-li se zeptat? :)
Chápu to jako celkový kontext/konfiguraci/kombinaci všech injectovaných záležitostí, které daný objekt, do kterého se to injectuje, využívá. Je to kontext, v rámci kterého je daný objekt vytvářený/volaný/prováděný.
Že jsem kverulant, ono by se to taky mohlo jmenovat IOC ;P
- kravčo
- Člen | 721
„Kontext“ vysvetľuje akademický slovník cudzích slov pod 2. ako súvislosť vôbec, súbor súvislostí v nejakom dianí; napr. kontext doby.
A v podstate súvislosť, či súbor súvislostí to je i v tomto prípade. K nejakému objektu pripojím jeho kontext – súbor súvisiacich objektov (NUser, NHttpRequest, …), ktoré môže transparentne využívať. Kontext mi zase umožní využívať DI, takisto môže zabezpečiť lazy loading a ďalšie fičúry…
- David Grudl
- Nette Core | 8227
Kernel se mi vážně nelíbí. Když jsi to tu prve psal, považoval jsem to za fórek.
Z pohledu .NET vývojáře můžeš mít pocit, že tu vynalézáme kolo a do jisté míry budeš mít pravdu. Ale tyto věci nejsou u PHP frameworků zaběhlé, existují tu trošku jiné potřeby a nemusíme ani opakovat chyby jiných implementaci (tj. uděláme si vlastní). Název kernel bych třeba za chybu názvosloví považoval.
- v6ak
- Člen | 206
IoC je popis stavu („je to naopak“) a je IMHO nevhodný pro název třídy. Celkově tento název nemám rád, protože on vlastně za normální označuje stav, který já považuji za nenormální. Ostatně, do zavedení DI jsem ve svém kódu viděl něco ošklivého, nenormálního, co by asi mělo být řešeno nějak jinak…
Kontext – proč ne? Něco provedu v kontextu aktuálního HTTP požadavku s aktuálním uživatelem. Nebo provedu něco jako $application->run() v kontextu jiného HTTP požadavku (nápad převzat ze článku Singleton Sofie S. ). Podstatné je, že tu není něco jako „globální kontext“ – onen vnitřní požadavek nemůže zjistit, jestli je vnitřní.
- rarous
- Člen | 59
No, když už tedy máte Texy, dibi, nette, proč nevymyslet krásný název i pro IoC kontejner? :)
Tvoje implementace (užívající lazy initialization pomocí factories) opravdu neni ServiceLocator, ale ani žádný Context.
Když to vezmu z pohledu implementací IoC v .NET, tak se používá WindsorContainer, který vnitřně použíná Kernel (MicroKernel), pak je tu Ninject, který má Kernel, StructureMap používá ObjectsFactory, Autofac používá Container, Unity nejspíš taky užívá termínu Container a Spring.net používá ApplicationContext.
Myslím si, že jednotný slovník s jinými platformami neni na škodu (i přes výhodu možnosti zavést si vlastní názvosloví).
- Blizzy
- Člen | 149
Context je dobrý název, ale pod instancí třídy Context si nepředstavím objekt, kam registruju a nastavuju jiné třídy, objekty a továrničky na objekty. Stále si stojím za zmíněným ServiceContainerem. Tento název používá i Symfony implementace DI containeru a je velmi podobný i se zmiňovanými .NET implementacemi. Nicméně kontext už má hodně hlasů, a můj názor na to asi nic nezmění :).
- Filip Procházka
- Moderator | 4668
Přečtěte si co napsal David a potom co napsal Honza Tichý, přečtěte si to ještě jednou a pokud furt nebude jasné jaký má mít ta třída význam tak se běžte podívat na github kde už je to předělané a zvykejte si :)
- Lopata
- Člen | 139
Nevím, jestli nejsem příliš zhýčkaný programátor nebo jestli vy
programujete v notepadu, ale IDE vám stejně napovídá, takže tu délku bych
zas tak horkou neviděl. Myslím, že i kdyby se to nakonec jmenovalo
Nejneobhospodarovavatelnejsimi
, každý stejně napíše „Nej“,
dolů enter…
Editoval Lopata (16. 9. 2010 22:55)
- Ondřej Mirtes
- Člen | 1536
Když už jsme u těch DI změn, tak se přimlouvám za dvoukrokový bootstrap, kdy si mezi prvním (matchování rout) a druhým (spuštění presenteru) budu moci do presenteru vstříknout context (a zároveň v něm budu moci využít informace z proběhnutého routování, typicky locale). Nyní je potřeba to dělat přes Environment, což je nešikovné.
Taky předpokládám, že $this->context
bude přistupný
i v controlech, předávaný nejspíš jako parametr konstruktoru (záměrně
neříkám kolikátý) nebo addComponent. On bude muset být přístupný třeba
i ve formulářích, které pracují s HttpRequest, že :)
Určitě bych do contextu přidal i config.
A ještě drobnost k API – abych v aktuálním objektu contextu vyměnil jednu implementaci nějakého rozhraní za jinou (kvůli čemuž to vstřikování vlastně dělám), musel bych se zeptat na hasService, případně provést removeService a nakonec addService, což je nepohodlné. Šla by zavést metoda setService, která by se nekoukala, zdali v objektu už ta daná implementace je, a prostě ji bez ohledu na ni přebyla?
- David Grudl
- Nette Core | 8227
HosipLan napsal(a):
Přečtěte si co napsal David a potom co napsal Honza Tichý, přečtěte si to ještě jednou a pokud furt nebude jasné jaký má mít ta třída význam tak se běžte podívat na github kde už je to předělané a zvykejte si :)
Ale kurňa, to mi tam uletělo z vývojové větvě…
- Honza Marek
- Člen | 1664
Ne, že bych měl někomu ve zvyku kecat do názvosloví. Na to se necítím být dostatečně vzdělán ve stopadesáti programovacích jazycích a frameworcích. Ale co to sakra je context?
- Filip Procházka
- Moderator | 4668
Honza Marek:
imho to bude třída, která bude poskytovat konrétnímu objektu služby,
které s ním nějak souvisí
Jak máš teď $defaultServices
v Application
, tak
to s tím bude pravděpodobně nějak souviset.
Dalším třídám se to bude asi přes konstruktor předávat, například
třídě Control
nebo Presenter
.
Čímž docílíš například (a nevím jestli je to nejvhodnější příklad)
toho, že nebudeš moct „systémově“ šahat z komponenty do Routeru,
protože bude přes Context
přístupný pouze v
Application
, takže pak nepůjde
$presenter->context->getRouter();
ale
$presenter->application->context->getRouter();
(vím že
to jde teď $application->getRouter()
). Bude zkrátka
přístupné pouze to co s třídou souvisí.
Chápu to správně? :)
PS: Router je blbý příklad, ale lepší se mi nechce vymýšlet :P
- na1k
- Člen | 288
HosipLan, a kde nadefinuju, které služby souvisí s kterým objektem?
Asi mi pořád uniká smysl této změny. Proč nestačí zrušit
Environment
a jeho „služby“ přesunout do
ServiceLocator
u, které by byly přístupné např. v
Control
přes
$this->locator->getService('Nette\Web\User')
?
(Vlákno jsem četl asi třikrát…)
- David Grudl
- Nette Core | 8227
ad Container vs. Context: vidím tady určitý implementační rozdíl, zatímco pod pojmem Container se v DI rozumí ten chytrý objekt co ví, jak poskytované objekty sestavit, Context je spíš jen malinko chytřejší přenašeč. Ironií je, že o to více mi připadá zaběhlý pojem Container v DI jako špatný.
- Filip Procházka
- Moderator | 4668
na1k: nejsi sám koho to zajímá :) pár teorií jak to
udělat mám,
ale nepochybuju že David vymyslí něco lepšího, takže se nebudu ani snažit
to rozepisovat :)
- David Grudl
- Nette Core | 8227
na1k napsal(a):
Proč nestačí zrušit
Environment
a jeho „služby“ přesunout doServiceLocator
u, které by byly přístupné např. vControl
přes$this->locator->getService('Nette\Web\User')
?
Nejen, že to stačí, ono už to tam dávno je. Ale nikdo to nepoužívá. Proto chci věc zjednodušit, upravit, osvěžit, navonět a hlavně dotáhnout nedostatky do konce.
HosipLan napsal(a):
ale nepochybuju že David vymyslí něco lepšího, takže se nebudu ani snažit to rozepisovat :)
Ale jen se rozepisuj, vždyť tohle je takový brainstorming a patent na rozum skutečně nemám.