Miroslav Holec
Premium

Cesta k ASP.NET Core 2.1, ASP.NET Core Runtime a Shared Frameworks

Miroslav Holec   4. května 2018

Článek se vztahuje k verzi produktu ASP.NET Core 2.1

Tento článek byl napsán v roce 2018. Vývojářské technologie se neustále inovují a článek již nemusí popisovat aktuální stav technologie, ideální řešení a můj současný pohled na dané téma.

Webový framework ASP.NET Core je s námi již relativně dlouhou dobu, během které si prošel mnoha změnami. Řada výhod, které byly při jeho zrození zmiňovány přinesla celkem očekávané potíže, které se průběžně řešily. Za poslední dva roky tyto změny vytvořily zajímavou historii, se kterou vás v tomto článku seznámím.

ASP.NET Core 1.0 a vize modulárního frameworku

Na úplném počátku byl ASP.NET Core 1.0 oslavován jako velmi modulární framework, jehož jednotlivé funkce byly extrahovány do řady samostatných NuGet balíčků. Tím se v pozitivním světle ASP.NET Core liší od starého ASP.NET frameworku, jehož podstatná část žije pouze v několika assemblies, z čehož tou "nejnadupanější" je System.Web.

Modularita působila dobře do té doby, než vývojář začal skutečně psát nějakou aplikaci. Pokud použil předpřipravenou šablonu pro webovou aplikaci, měl k dispozici řadu NuGet balíčků, které z větší části stačily na vytvoření jednoduché webové aplikace. Nepříjemné jen bylo občas hledat, kde se ta či ona funkce nachází (v jakém balíčku), naštěstí však při nás vždy stála podrobná dokumentace, od které by se mohlo plno vývojářských fabrik učit.

Když přišly první aktualizace

ASP.NET Core protrpěl těžké porodní bolesti, na které prakticky okamžitě navázalo záplatování jednotlivých balíčků. Ty byly velmi pružně uvolňovány. Uteklo několik týdnů a zatímco některé NuGet balíčky už byly vydány ve verzi 1.0.3, jiné byly 1.0.2 a jiné méně záplatované 1.0.1. Vývojář, který tak začal provádět aktualizace projektu okamžitě ztratil povědomí o tom, jaké balíčky jsou v jaké verzi. V balíčcích se navíc začaly objevovat odlišnosti v API, a tudíž na sebe balíčky přestávaly občas pasovat. Nastalo balíčkovací peklo a málokdo (tedy téměř nikdo) již nebyl schopen poskládat smečku balíčků tak, aby komplexně spolupracovala. NuGet package manager sice uměl ledacos vyřešit, trvalo mu to však stále delší dobu, jelikož kombinatorická úloha při desítkách balíčků dala zabrat i vyspělému Redmontskému softwaru. Verze balíčků se nakonec na krátký čas narovnaly s příchodem ASP.NET Core 1.1, nicméně historie se opakovala a velmi rychle začal vznikat ten samý chaos jako u ASP.NET Core 1.0.

ASP.NET Core 2.0 a metabalíček Microsoft.AspNetCore.All

Řád uprostřed chaosu nastolil metabalíček Microsoft.AspNetCore.All, který sám o sobě vlastně jen obsahoval seznam závislostí (NuGet balíčků), které dohromady tvoří stabilní smečku NuGet balíčků. Vývojář už se tak nemusí příliš starat o verze jednotlivých částí frameworku a v podstatě jen aktualizuje jeden metabalíček All. Ten obsahuje celou řadu balíčků tvořících ASP.NET Core včetně souvisejících 3rd party knihoven. Vývojář tak dostává kompletní framework, včetně Application Insights SDK, podpory StackExchange.Redis, Newtonsoft.Json a dalších Azure specific knihoven. Jak asi správně tušíte, balík je to pekelně velký.

Runtime Store aneb novodobá GAC

Kromě modularity měla být od počátku velkou výhodou schopnost spustit ASP.NET Core aplikace izolovaně na jednom stroji a nezávisle na verzi frameworku. V podstatě tak bylo možné vypublikovat si 10 aplikací, přičemž každá si s sebou mohla přibalit runtime včetně všech balíčků pro svůj šťastný život. Velikost metabalíčku Microsoft.AspNetCore.All jde ale proti této výhodě. Nejen, že takto velký balík si s sebou každá aplikace musí táhnout, ale už samotný restore balíčků na vývojářském stroji se nepříjemně prodlužuje.

Microsoft proto společně s .NET Core 2.0 a ASP.NET Core 2.0 představuje tzv. Runtime Store. Na disku, kde je instalován .NET Core SDK se nově usídlila globální složka store, která obsahuje kompletně všechny NuGet balíčky z Microsoft.AspNetCore.All. Vývojář při publikování balíčku nyní může zvolit, že nechce do publikovaného balíčku přidat standardní ASP.NET Core knihovny, ale na místo toho použije knihovny ze sdíleného store. Při publikaci jsou tyto možnosti rozlišeny jako Framework-Dependent VS Self-Contained. Store se tak stává složkou, kterou může využívat hned několik ASP.NET Core aplikací. Řada balíčků se navíc instaluje optimalizovaná pro daný stroj, což v konečném důsledku zrychluje startup time aplikací. Pro 3rd party libraries přibyla taktéž možnost nainstalování souborů zkompilovaných pro daný stroj místo toho, aby musela probíhat kompilace JIT nad originálními assemblies (napíšu o tom brzy článek).

Další komplikace a výzvy

Při samotné publikaci typu Framework-Dependend zafunguje Output Trimmer. Ten defacto při publikování vyseká všechno, co je definované v metabalíčku Microsoft.AspNetCore.All a chybějící NuGety přibalí do výstupu (protože ve store nejsou). Jakmile tedy začnu sadu balíčků v aplikaci patchovat, začíná opět balíček růst a využití společného store klesá. S každým komplexním patchem celého metabalíčku navíc na disku vzniká další sada složek s novou verzí všech balíčků, čímž narůstá i samotný store. Pokud mám ASP.NET Core aplikaci využívající Microsoft.AspNetCore.All verze 2.0.1, musí tato verze být v runtime store. Novější verze 2.0.2 není použitelná, ačkoliv by se to mohlo zdát logické.

Z toho vyplývá i velmi nepraktické patchování. Pokud se objeví bezpečnostní chyba ve frameworku, která je opravena a dostupná v rámci Microsoft.AspNetCore.All verze 2.0.3, nemám jinou možnost než aktualizovat store a následně provést nový deploy zcela všech ASP.NET Core aplikací. Patchování komplikují i 3rd party knihovny, které jsou zcela mimo kontrolu Microsoftu, který je v dobré víře zahrnul do metabalíčku.

Rostoucí store se v neposlední řadě stává zcela nekontrolovatelným kontejnerem různých verzí a vývojář si nemůže dovolit z něj cokoliv bezpečně odstranit, aniž by tím neriskoval ohrožení běžících aplikací.

Dále se pojďme věnovat verzi ASP.NET Core 2.1, která zatím není public a některé věci tedy teoreticky mohou být ve finále trochu jinak.

ASP.NET Core 2.1 - Shared Frameworks / ASP.NET Core Runtime

Od verze ASP.NET Core 2.1 se situace opět trochu změní. Společně s novou verzí .NET Core Runtime se instaluje také ASP.NET Core Runtime. Ve složce dotnet/shared tak nově uvidíme tři složky:

  • Microsoft.NETCore.App - .NET Core framework
  • Microsoft.AspNetCore.All - metabalíček, viz. story výše
  • Microsoft.AspNetCore.App - jádro samotného webového frameworku

Metabalíček Microsoft.AspNetCore.App

Jedná se o nový metabalíček po vzoru Microsoft.NETCore.App, který obsahuje pouze nejdůležitější části ASP.NET Core, které jsou zcela pod kontrolou Microsoftu. Kromě toho závislosti, ze kterých je balíček tvořen jsou založeny na exaktních verzích, čímž je zajištěna stabilita klíčové části (jádra) frameworku, nazývaná jako Shared Framework.

Runtime se nově stará o patchování minor verzí frameworku. Pokud mám tedy ASP.NET Core 2.1.0 a aplikaci targetující stejnou verzi 2.1.0, mohu nainstalovat do runtime store verzi 2.2.0 a má aplikace se automaticky přetargetuje na vyšší minor verzi. Minor verze mohou obsahovat i nová API a funkcionalitu, nejedná se však o breaking changes a změny tak nenaruší chod aplikací (retargeting je bezpečný).

Původní balíček Microsoft.AspNetCore.All je stále dostupný, avšak nevyužívá exaktní verze a obsahuje stále závislosti na 3rd party knihovnách. Microsoft jej dále doporučuje nepoužívat - nahradit variantou Microsoft.AspNetCore.App.


Závěr

Historie ASP.NET Core je od samého začátku zajímavá a každá verze přináší nová překvapení a řešení komplikací, které se s každou verzí objeví. Změny, které přijdou ve verzi 2.1 nejsou na první pohled zřejmé a plno vývojářů si všimne maximálně nového metabalíčku Microsoft.AspNetCore.App. Přesto je dobré mít představu o tom, co se na pozadí děje.