Architekturális alapelvek

Tipp.

Ez a tartalom egy részlet az eBook, Architect Modern Web Applications with ASP.NET Core és Az Azure, elérhető a .NET Docs vagy egy ingyenesen letölthető PDF, amely offline olvasható.

Architect Modern Web Applications with ASP.NET Core and Azure eBook cover thumbnail.

"Ha az építők úgy építenek épületeket, ahogy a programozók programokat írtak, akkor az első fakopáncs, amely jött, elpusztítaná a civilizációt."
- Gerald Weinberg

A karbantarthatóságot szem előtt tartva érdemes szoftvermegoldásokat tervezni és tervezni. Az ebben a szakaszban ismertetett alapelvek segíthetnek az olyan architekturális döntések meghozatalában, amelyek tiszta, karbantartható alkalmazásokat eredményeznek. Ezek az alapelvek általában az alkalmazások különálló összetevőkből való kiépítéséhez vezetnek, amelyek nem szorosan kapcsolódnak az alkalmazás más részeihez, hanem explicit felületeken vagy üzenetkezelő rendszereken keresztül kommunikálnak.

Általános tervezési alapelvek

Az aggodalmak elkülönítése

A fejlesztés egyik vezérelve az aggodalmak elkülönítése. Ez az elv azt állítja, hogy a szoftvereket az általa végzett munkatípusok alapján kell elválasztani. Vegyük például azt az alkalmazást, amely a felhasználó számára megjelenítendő figyelemre méltó elemek azonosítására szolgáló logikát tartalmaz, és amely az ilyen elemeket egy adott módon formázva teszi láthatóbbá. A formázandó elemek kiválasztásáért felelős viselkedést el kell különíteni az elemek formázásáért felelős viselkedéstől, mivel ezek a viselkedések olyan különálló aggodalmak, amelyek csak véletlenül kapcsolódnak egymáshoz.

Architekturálisan az alkalmazások logikailag úgy hozhatók létre, hogy ezt az elvet követve elválasztják az alapvető üzleti viselkedést az infrastruktúrától és a felhasználói felület logikájától. Ideális esetben az üzleti szabályoknak és a logikának külön projektben kell elhelyezkedniük, ami nem függhet az alkalmazás más projektjeitől. Ez az elkülönítés segít biztosítani, hogy az üzleti modell könnyen tesztelhető legyen, és fejlődhessen anélkül, hogy szorosan össze lennének kapcsolva az alacsony szintű megvalósítás részleteivel (az is segít, ha az infrastruktúra-aggodalmak az üzleti rétegben meghatározott absztrakcióktól függnek). Az aggodalmak elkülönítése kulcsfontosságú szempont a rétegek alkalmazásarchitektúrákban való használata mögött.

Beágyazás

Az alkalmazás különböző részeinek beágyazást kell használniuk, hogy elszigeteljék őket az alkalmazás más részeitől. Az alkalmazásösszetevőknek és -rétegeknek képesnek kell lenniük a belső megvalósításuk módosítására anélkül, hogy megszegik a közreműködőiket, feltéve, hogy a külső szerződések nem sérülnek. A beágyazás megfelelő használata segít laza csatolást és modularitást elérni az alkalmazástervekben, mivel az objektumok és csomagok helyettesíthetők alternatív implementációkkal mindaddig, amíg ugyanaz a felület megmarad.

Az osztályokban a beágyazás az osztály belső állapotához való külső hozzáférés korlátozásával érhető el. Ha egy külső szereplő módosítani szeretné az objektum állapotát, azt egy jól definiált függvényen (vagy tulajdonságválasztón) keresztül kell elvégeznie, nem pedig az objektum privát állapotához való közvetlen hozzáféréssel. Hasonlóképpen az alkalmazásösszetevőknek és maguk az alkalmazásoknak is jól definiált interfészeket kell elérhetővé tenniük a közreműködők számára, ahelyett, hogy lehetővé tennék az állapotuk közvetlen módosítását. Ez a megközelítés lehetővé teszi, hogy az alkalmazás belső kialakítása idővel fejlődjön, anélkül, hogy emiatt aggódnia kellene, hogy ez megtöri a közreműködőket, mindaddig, amíg a közbeszerzési szerződések megmaradnak.

A mutable global state antithetical to encapsulation. Az egyik függvényben a mutable globális állapotból lekért értékre nem lehet hivatkozni arra, hogy ugyanaz az érték szerepeljen egy másik függvényben (vagy még ennél is tovább ugyanabban a függvényben). A globális állapottal kapcsolatos aggodalmak megértése az egyik oka annak, hogy a C#-hoz hasonló programozási nyelvek támogatják a különböző hatókörkezelési szabályokat, amelyeket az utasításoktól a metódusokon át az osztályokig mindenhol használnak. Érdemes megjegyezni, hogy azok az adatvezérelt architektúrák, amelyek egy központi adatbázisra támaszkodnak az alkalmazásokon belüli és azok közötti integrációhoz, maguk is az adatbázis által képviselt mutable globális állapottól függnek. A tartományalapú tervezés és a tiszta architektúra egyik legfontosabb szempontja az adatokhoz való hozzáférés beágyazása, valamint annak biztosítása, hogy az alkalmazás állapota ne legyen érvénytelen a megőrzése formátumához való közvetlen hozzáféréssel.

Függőség inverziója

Az alkalmazáson belüli függőség irányának az absztrakció irányába kell lennie, nem pedig a megvalósítás részleteinek. A legtöbb alkalmazás úgy van megírva, hogy a fordítási idő függőségi folyamatai a futtatókörnyezet végrehajtásának irányában haladnak, és közvetlen függőségi gráfot eredményeznek. Vagyis ha az A osztály a B osztály metódusát hívja meg, a B osztály pedig a C osztály metódusát hívja meg, akkor fordításkor az A osztály a B osztálytól, a B osztály pedig a C osztálytól függ, ahogyan az a 4–1. ábrán látható.

Direct dependency graph

4-1. ábra. Közvetlen függőségi diagram.

A függőségi inverzió elvének alkalmazása lehetővé teszi az A számára, hogy metódusokat hívjon a B által implementált absztrakción, lehetővé téve, hogy az A futásidőben hívja meg a B-t, de a B a fordítási időpontban az A által felügyelt felülettől függjön (így megfordítva a tipikus fordítási idő függőségét). Futásidőben a program végrehajtásának folyamata változatlan marad, de az interfészek bevezetése azt jelenti, hogy ezeknek a felületeknek a különböző implementációi könnyen csatlakoztathatók.

Inverted dependency graph

4–2. ábra. Fordított függőségi gráf.

A függőségi inverzió kulcsfontosságú része a lazán összekapcsolt alkalmazások létrehozásának, mivel a megvalósítás részletei megírhatók a magasabb szintű absztrakcióktól való függésre és implementálásra, nem pedig fordítva. Az eredményként kapott alkalmazások tesztelhetőbbek, modulárisabbak és karbantarthatók. A függőséginjektálás gyakorlata a függőség inverziós elvének követésével lehetséges.

Explicit függőségek

A metódusoknak és osztályoknak explicit módon kell megkövetelniük az összes együttműködő objektumot, amelyekre szükségük van a megfelelő működéshez. Ezt explicit függőségi elvnek nevezzük. Az osztálykonstruktorok lehetőséget biztosítanak az osztályok számára, hogy azonosítsák azokat a dolgokat, amelyekre szükségük van ahhoz, hogy érvényes állapotban legyenek és megfelelően működjenek. Ha olyan osztályokat határoz meg, amelyek létrehozhatók és meghívhatók, de ez csak akkor működik megfelelően, ha bizonyos globális vagy infrastruktúra-összetevők vannak érvényben, ezek az osztályok tisztességtelenek az ügyfeleikkel. A konstruktorszerződés azt mondja az ügyfélnek, hogy csak a megadott dolgokra van szüksége (valószínűleg semmit sem, ha az osztály csak paraméter nélküli konstruktort használ), de futásidőben kiderül, hogy az objektumnak valóban szüksége van valami másra.

Az explicit függőségek elvének követésével az osztályai és módszerei őszinteek az ügyfeleikkel arról, hogy mire van szükségük a működéshez. Az elvet követve a kód önaláírásosabbá válik, a kódolási szerződések pedig felhasználóbarátabbak lesznek, mivel a felhasználók megbízhatnak abban, hogy amíg a metódusok vagy konstruktorparaméterek formájában megadják a szükséges adatokat, az általuk dolgozott objektumok futásidőben megfelelően fognak viselkedni.

Önálló felelősség

Az egyetlen felelősség elve az objektumorientált tervezésre vonatkozik, de az aggodalmak elkülönítéséhez hasonló architektúraelvnek is tekinthető. Azt állítja, hogy az objektumoknak csak egy felelősségi körük kell, hogy legyen, és csak egy oka legyen a változásra. Pontosabban az egyetlen helyzet, amelyben az objektumnak változnia kell, ha az egyetlen felelősségi körébe tartozó feladat elvégzésének módját frissíteni kell. Ennek az elvnek a követése segít lazán összekapcsolt és moduláris rendszerek létrehozásában, mivel sokféle új viselkedés implementálható új osztályokként, ahelyett, hogy további felelősséget ad a meglévő osztályokhoz. Az új osztályok hozzáadása mindig biztonságosabb, mint a meglévő osztályok módosítása, mivel egy kód sem függ az új osztályoktól.

Monolitikus alkalmazásokban magas szinten alkalmazhatjuk az egységes felelősség elvét az alkalmazás rétegeire. A megjelenítési felelősségnek a felhasználói felületi projektben kell maradnia, míg az adathozzáférési felelősséget egy infrastruktúraprojekten belül kell tartani. Az üzleti logikát az alkalmazás alapprojektjében kell tartani, ahol könnyen tesztelhető, és más feladatoktól függetlenül fejlődhet.

Ha ezt az elvet alkalmazza az alkalmazásarchitektúrára, és annak logikai végpontjára viszi, mikroszolgáltatásokat kap. Egy adott mikroszolgáltatásnak egyetlen felelősségi körrel kell rendelkeznie. Ha ki kell terjesztenie egy rendszer viselkedését, általában jobb, ha további mikroszolgáltatásokat ad hozzá ahelyett, hogy felelősséget ad egy meglévőhöz.

További információ a mikroszolgáltatások architektúrájáról

Ne ismételje meg önmagát (DRY)

Az alkalmazásnak kerülnie kell egy adott fogalomhoz kapcsolódó viselkedés megadását több helyen, mivel ez a gyakorlat gyakori hibaforrás. Egy bizonyos ponton a követelmények megváltozásához módosítani kell ezt a viselkedést. Valószínű, hogy a viselkedés legalább egy példánya nem lesz frissítve, és a rendszer inkonzisztensen fog viselkedni.

A logika duplikálása helyett ágyazza be egy programozási szerkezetbe. Hozza létre ezt a konstrukciót a viselkedés felett, és az alkalmazás bármely más olyan részével rendelkezzen, amely megköveteli ezt a viselkedést, használja az új konstrukciót.

Feljegyzés

Kerülje a csak véletlenül ismétlődő kötési viselkedést. Például csak azért, mert két különböző állandó ugyanazzal az értékkel rendelkezik, ez nem jelenti azt, hogy csak egy állandóval kell rendelkeznie, ha elméletileg különböző dolgokra hivatkoznak. A duplikáció mindig előnyösebb a rossz absztrakcióhoz való csatlakozáshoz.

Adatmegőrzési tudatlanság

A megőrzési tudatlanság (PI) olyan típusokat jelent, amelyeket meg kell őrizni, de amelyeknek a kódját nem érinti a megőrzési technológia kiválasztása. A .NET ilyen típusait néha egyszerű régi CLR-objektumoknak (POCOS-oknak) is nevezik, mert nem kell örökölniük egy adott alaposztályt, és nem kell implementálniuk egy adott felületet. A megőrzési tudatlanság azért értékes, mert lehetővé teszi, hogy ugyanazt az üzleti modellt több módon is megőrizze, ami további rugalmasságot biztosít az alkalmazás számára. Az adatmegőrzési lehetőségek idővel változhatnak egy adatbázis-technológiáról egy másikra, vagy az alkalmazás használatán kívül további adatmegőrzési formákra is szükség lehet (például Redis Cache vagy Azure Cosmos DB használata a relációs adatbázis mellett).

Néhány példa ennek az elvnek a megsértésére:

  • Kötelező alaposztály.

  • Egy szükséges felületi implementáció.

  • Saját maguk mentéséért felelős osztályok (például az Aktív rekord minta).

  • Kötelező paraméter nélküli konstruktor.

  • Virtuális kulcsszót igénylő tulajdonságok.

  • A perzisztencia-specifikus kötelező attribútumok.

Az a követelmény, hogy az osztályok a fenti funkciók vagy viselkedések bármelyikével rendelkezzenek, összekapcsolja a megőrizendő típusokat és a megőrzési technológia választását, ami megnehezíti az új adathozzáférési stratégiák bevezetését a jövőben.

Kötött környezetek

A határolt környezetek a tartományalapú tervezés központi mintái. Lehetővé teszik a nagy alkalmazások vagy szervezetek összetettségének kezelését azáltal, hogy külön elméleti modulokra bontják. Minden fogalmi modul ezután egy olyan környezetet jelöl, amely elválasztva van más környezetektől (így határolódva), és egymástól függetlenül fejlődhet. Minden határolt környezetnek ideális esetben szabadon meg kell adni a saját nevét a benne lévő fogalmakhoz, és kizárólagos hozzáféréssel kell rendelkeznie a saját adatmegőrzési tárolóhoz.

Legalább az egyes webalkalmazásoknak arra kell törekedniük, hogy saját, kötött környezetük legyen, és ne egy adatbázist osszanak meg más alkalmazásokkal, hanem saját, üzleti modelljükhöz tartozó adatmegőrzési tárolóval. A kötött környezetek közötti kommunikáció programozott felületeken, nem pedig megosztott adatbázison keresztül történik, ami lehetővé teszi, hogy az üzleti logika és események a változásokra reagálva történjenek. A kötött környezetek szorosan kapcsolódnak a mikroszolgáltatásokhoz, amelyek ideális esetben saját, kötött környezetként is implementálhatók.

További erőforrások