Keretrendszer-frissítés egy legacy nagyvállalati alkalmazásban
Egy keretrendszer-frissítés egy több mint 10 éves, összetett, erősen integrált vállalati alkalmazásában nemcsak technikai, hanem stratégiai kihívás is.
Az alábbiakban arról lesz szó, hogyan frissítettünk egy legacy nagyvállalati alkalmazást .NET Framework-ről .NET 8-ra, és milyen problémákkal kellett megküzdenünk. Bár maga az alkalmazás nem volt óriási, annál összetettebb volt, szorosan integrálva közel két tucat másik rendszerrel.
Mivel nem volt sem költségvetésünk, sem időnk, sem hatáskörünk a kapcsolódó rendszerek frissítésére vagy módosítására, a fő cél az volt, hogy a lehető legnagyobb mértékben megőrizzük a visszafelé kompatibilitást.

A folyamat
Mielőtt belevágtunk volna, pontosan meghatároztuk a projekt kereteit. A cél egy keretrendszer-frissítés volt, a meglévő üzleti logika változtatása nélkül. Az alkalmazás több modulból állt:
- egy Windows Service az ütemezett feladatok kezelésére,
- WCF (Windows Communication Foundation) szolgáltatások a többi rendszerrel és a belső hálózati tartományok között történő kommunikációra,
- két ASP.NET MVC weboldal – egy a belső és egy a külső felhasználók számára.
Felmerült, hogy a frontendet újraírjuk egy modern JavaScript-keretrendszerben, de időhiány és a refaktorálás limitált előnyei miatt ezt elvetettük. Hasonló okból az alapvető architektúrát is megtartottuk.
Egy egyszerűbb .NET Framework alkalmazás frissítése akár néhány kattintásból is megoldható lehet, de mi sajnos nem voltunk ilyen egyszerű helyzetben. Lépésről lépésre haladtunk, egyesével frissítve a projekteket a függőségi fa mentén. Egy teljesen üres solution-ből indultunk ki, minden projektet újra létrehoztunk, a fájlokat átvittük, a harmadik féltől származó függőségeket feloldottuk, és szükség szerint refaktoráltuk.
Ez egy lassú és fárasztó folyamat volt, de teljes kontrollt adott a függőségek, interfészek és refaktorálási döntések felett.
Először a cross-cutting réteget frissítettük, majd az adat-hozzáférési réteget. Korábban database-first megközelítést használtunk, most áttértünk code-first-re, hogy a séma-kezelés egyszerűbb legyen. Visszafejtettük az adatbázisból az entitásosztályokat és elkészítettük a szükséges migrációkat.
Ezután jött az üzleti logika réteg, ami viszonylag gond nélkül ment.
Miután végeztünk az alsóbb rétegekkel, végre párhuzamosan dolgozhattunk a felső szintű modulokon: a Windows Service-en, a WCF szolgáltatásokon és a weboldalakon.
Az ASP.NET MVC → ASP.NET Core MVC frissítés önmagában nem túl bonyolult, de az authentikációs és autorizációs réteg teljesen egyedi módon volt megvalósítva különböző kontrollerekben, interceptor-okban és a Global.asax fájlban. Ezt teljesen újra kellett írni middleware-ek segítségével.
A WCF öröksége
A legnagyobb kihívást kétségkívül a WCF jelentette. A Microsoft láthatóan távolodik tőle, és .NET 8-ban nem teljes a támogatás. Ugyanakkor a nagyvállalati világban még mindig rengeteg helyen használják. A saját alkalmazásunkban a WCF-et három fő célra alkalmaztuk:
- Alkalmazásmodulok közötti belső kommunikáció – mivel itt nem volt külső függőség, így REST API-ra váltottunk. A korábban WCF Behavior-rel megvalósított logikát HTTP header-eken és middleware-eken keresztül vittük át.
- Adatlekérés más rendszerekből – ebben az esetben maradnunk kellett a WCF-nél. Újrageneráltuk a WCF klienseket .NET 8-ban, és szinte hibátlanul működtek. Egyetlen komoly gond volt: bizonyos WCF binding és IIS beállítás-kombinációknál nem sikerült kiválasztani a megfelelő Windows authentication provider-t. A megoldás az volt, hogy egyedi HTTP message handlerrel kikényszerítettük a helyes authentication provider-t. Hogy megőrizzük a WCF testreszabhatóságát az XML-konfigurációhoz hasonló beállítási lehetőségeket építettünk be JSON-be.
- Más rendszerek által elérhető interfészek publikálása – itt szintén WCFnél kellett maradnunk, mert a kliensrendszereket nem frissíthettük. .NET 8-ban ehhez nincs hivatalos támogatás, viszont létezik egy közösség által fenntartott port: CoreWCF. Ez majdnem tökéletesen megfelelt az igényeinknek, csak egy kisebb gond akadt: nem tudta értelmezni a hívó által küldött impersonation level-t. Szerencsére ezt a hívó oldalon, kódmódosítás nélkül, konfigurációból állítva tudtuk kezelni.
Egyéb kihívások
Bár a WCF volt a legnagyobb kihívás (ezeket a kérdéseket még a projekt indulása előtt le kellett zárnunk, mert ezek lezárása nélkül a projekt el sem kezdődhetett volna), számos más technikai akadállyal is találkoztunk:
- A kódbázis több mint 10 éves volt, így sem dependency injection, sem async-await nem volt benne, amik viszont a .NET 8-as alkalmazásokban széles körben elterjedtek. A megújítás során mindkettőt bevezettük, ami nagy mértékű kódmódosítást igényelt. A DI bevezetésében a legnagyobb kihívást a körkörös hivatkozások okozták, emiatt több helyen is a kód refaktorálására volt szükség. Az aszinkronitás bevezetése körben rátaláltunk egy SQLClient hibára, ami a byte tömbök aszinkron lekérdezéseknél szignifikáns lassulást okozott, így ezeket visszaváltottuk szinkronra.
- Először bekapcsoltuk a nullable reference type-okat, ami a REST API kérés-ellenőrzések esetén is bekapcsolta a null ellenőrzést. Ez azt jelentette, hogy amennyiben bizonyos futási ágakon egy attribútum null értéket kapott, akkor az validációs hibához vezetett. Az eredeti applikáció nem a nullable reference type paradigma mentén került elkészítésre és több száz paraméter esetén nem tudtuk eldönteni, melyik lehet null valamilyen specifikus esetben, ezért végül itt csak a figyelmeztetéseket kapcsoltuk be.
- A régi kód BinaryFormatter-t használt, ami biztonsági szempontból már régóta nem ajánlott. .NET 8 esetén még engedélyezhető a használata egy kapcsoló átállításával, azonban biztonságosabb volt teljesen eltávolítani az alkalmazásból. JSON-serializációra váltottunk, de mivel a byte tömbök adatbázisban is tárolódtak, ezért adatokat is migrálnunk kellett.
- Egy meglepő gondot egy .NET 8-as bugfix okozott: a ToString(„F99”) formázás a .NET Framework hibája miatt eddig nullákkal töltötte ki a 15. számjegy után a double értékből generált string-et. Most viszont valós, de a mi rendszerünkben kezelhetetlen értékek kerültek be, így nem volt más választásunk, mint megpróbálni a régi viselkedést utánozni.
Összegzés: mit tanultunk?
A frissítési folyamat során több kihívással is szembe kellett néznünk és ezek megoldása időnként komplex volt, de végül megérte.
Rengeteget tanultunk a .NET Framework és .NET Core közötti különbségekről, a belső működésükről és néhány kevéssé ismert részletről. A Microsoft sokat tett azért, hogy az alkalmazásfejlesztés egyszerűbb és jobban átlátható legyen, például az alkalmazás indításakor lefutó folyamatok és konfigurációk létrehozása során. Bár még vannak funkciók, amik nem találhatóak meg benne, a .NET 8 már a legtöbb igényt lefedi. Ezek mellett sok új funkciót biztosít, mint például a beépített DI és az egyszerűsített Web API készítés.
A kódbázisunkat is jobban megismertük, bizonyos funkciókat refaktoráltunk, és több mint 15 ezer sor felesleges kódot töröltünk. A projekt fejlesztése és regressziós tesztelés során rengeteg régi hibát fedeztünk fel és javítottunk. Ezáltal egy korszerűbb, könnyebben karbantartható rendszert kaptunk.
Az Ön cégénél is vannak hasonló, frissítésre szoruló rendszerek, amelyekhez a komplex kihívások miatt nehéz hozzányúlni? Miért ne beszélgessünk erről egy kávé mellett?

