Tak předně děkuji moc za odpověď, trošku jsem popravdě doufal v těch nejtajnějších představách, že mi odpovíte právě vy :-)) .... což je na webu řešení, které zkousne i background vlákna, která můžete vytvořit (i když pro cokoliv reálného je stejně nedoporučuji používat). Jste si tímto jistý? Nebo,.. nevím ejstli to správně chápu, ale pokud vytvořím (někdo mi vytvoří nové vlákno) pomocí například TaskFactory.StartNew, tak se mu přeci implicitně nepřesype ThreadLocal storage? Je možné to snad nějak globálně ovlivnit? Špatně. Jestli je něco antipattern, tak je to používání UOW v prezentační vrstvě, kterou controller nepochybně je. Controller má posbírat data z UI, zavolat funkci ve fasádě, která vrátí výsledky, a ty controller nějak aplikuje do UI. Zde jsem se nad tímto hodně zamýšlel.. Jako příklad (možná špatný) si vzal WebAPI a rest, který by měl být plně bezstavový, tedy neměla by být "transakce" mezi více requesty.. jinými slovy 1 request = 1 transakce (byznys transakce). Pokud tedy takto konkrétní request definuje, že chci udělat konkrétní věc, proč nemůže reques celý definovat, že chci udělat nějakou věc zaráz? Vím moc dobře, že se transakce (je jedno jestli uow, nebo jiná) definuje na fasádě.. ale pokud si vezmu 3 ruzne requesty: 1) Otevřít krabičku 2) Zavřít krabičku 3) Otevřít krabičku, sníst bonbon a zavřít krabičku Neměla by být správně definována byznys transakce v samotné akci controlleru? Tj. 1) uow { otevriKrabicku() } 2) uow { zavriKrabicku() } 3) uow { otevriKrabicku() snezBonbon() zavriKrabicku() } ? To jen taková myšlenka k debatě... Používat objekty, které nejsou thread safe (což je třeba příklad DbContextu, UserStore nebo dalších věcí, které na nich silně závisí, např. i těch repository), by se také nemělo. Do jiného vlákna byste entity ani kontext neměl předávat nikdy, a u async/await musíte hlídat, jak to konkrétní metoda interně dělá... Tomu rozumím.. jsem si vědom toho, že samotný DBContext není threadsafe a proto (mimo jiné) musí být u každé async operace ihned await. Pokud se kouknu na to, co dělá uvnitř identity framework, konkretne napr. v extension metode:
public static IdentityResult Update<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
Tak zjistím, že on z metody, která je původně async udělá synchronní poněkud zajímavým způsobem:
return AsyncHelper.RunSync<IdentityResult>((Func<Task<IdentityResult>>) (() => manager.UpdateAsync(user)));
A zde dojde k onomu nestastnemu ztraceni contextu.. at už HttpContextu, nebo LocalThread storage.. Ohledně toho UserStore - já to řeším tak, že jsem si jej podědil a DbContext mu předávám přímo v konstruktoru v době, kdy vzniká (volám tam unitOfWorkProvider.GetCurrent().Context), a UserManager do fasád injektuji vždy přes Func<UserManager>, takže mám pak kontrolu nad tím, kdy vzniká store a vím, že dostane správný kontext (vyrobím si instanci user manageru až uvnitř unit of work). A ještě je dobré tomu store říct, aby po disposnutí context nechalo být a nedisposovalo ho, je tam na to nějaká property. Toto mě také napadlo, že tomuto storu (mmožná by stálo za to to udělat pro všechny) "dám" context hned v constructoru a nebudu ho getovat pomocí getteru, který si o to říká z UOW. Ohledně toho vytváření ve fasádě toho usermanageru.. já používám na authorizaci AuthorizationServer provider a ten se vytvoří pouze jednou při startu serveru (definováno v AppStart), takže tam musím jednotlivé "manažery" dopravit také pomocí nějaké factorky. Píšete, že si vytváříte instanci user manageru až uvnitř unity of work.. já vnímám user manager (mám ho poděděný) už jako konkrétní app servicu (tuším, že tomu říkáte UI facade), tedy bych měl správně asi UOW zakládat v každé metodě toho UserManageru, z čehož vyplývá že bych musel "opatchovat" každou metodu toho UserManagera, nebo použít nějaký method interception a to se mi zrovna asi moc nechce.. Takže to teď řeším tak, že AuthorizationServerProvider a jeho metody - tam vytvářím UOW. Bohužel uvnitř se volá UserManager a ten tam dělá ty triky s vlákny, takže se tam vytratí ten http/thread context.. verim ale, ze to getnutí si DBContextu už v constructoru, by mohlo vyřešit moje problémy. Mně se nelíbí zaprasit si polovinu funkcí v aplikaci tím, že jako první nebo poslední parametr budou přijímat unit of work. UOW v době, kdy vznikají instance, které ji potřebují, ještě neexistuje, tudíž se nedá nijak rozumně injektovat, můžete injektovat jen toho unit of work registry, který vám ji nějak musí dát. Zcela a plně souhlasím, to je právě důvod, proč třeba mám věci jako IUserProvider a pak implementaci ve Webovém projektu, který vrátí nějaký obecný "UserDTO", ke kterému si musí vytáhnout Identity.User. Abych si nemusel do všech servis atp, posílat current usera. ------------------------------------------------------ ------------------------------------------------------ Pokud se ještě můžu na něco zeptat k té prezentaci ... spíše si myslím, že by vás mohly zajímat moje otázky... Viděl jsem, že používáte QueryObjecty, ale ty jsou přímo natvrdo refnuté ve fasádách,.. mám za to, že princip této abstrakce - ať už v podobě generic repozitáře, TAK PŘEDEVŠÍM query objectu (používám namísto query objectu poděděný generic repo IProjectStore -> ProjectStore, kde si nadefinuju další metody) je zakrýt konkrétní implementaci tak, abych ji v případě problémů mohl například patchnout (tedy například nahradit getování přes EF a LINQ za přímý SQL pomocí např. ExecuteCommandu, případně volání stored procedury atp.. Pokud použiji přímo ve fasádě NecoQuery, nejsem pak schopný tuto implementaci vyměnit za jinou, aniž bych musel upravit všechny fasády,.. jelikož tam nemám ten DI,.. pokud však například toto "query" umístím jako metodu do repa (které je zakryté za Interfacem), nic mi nebrání v případě problémů si třeba repository podědit a overridnout jen danou metodu.. pak změním registraci v DI a jedu dále..? Jasně,. má to tu velkou nevýhodu, že můžu mít takto 1 dotaz X krát v různých repozitářích a je opravdu těžký správně určit kde by to asi tak mělo být (hledat správné agregátory v tabulkách), na druhou stranu, pokud budu chtít takto abstrahovat Query, tak to pro mě znamená vytvořit kromě 50ti tříd ještě 50 interfacu? Mějte prima večer
|