Sdílet prostřednictvím


Architektonické zásady

Návod

Tento obsah je výňatek z eBooku, architekt moderní webové aplikace s ASP.NET Core a Azure, který je k dispozici na .NET Docs nebo jako bezplatný soubor PDF ke stažení, který si můžete přečíst offline.

Navrhování moderních webových aplikací pomocí úvodní miniatury ASP.NET Core a Azure eBooku

"Pokud tvůrci postavili budovy tak, jak programátoři napsali programy, pak první woodpecker, který přišel, by zničil civilizaci."
- Gerald Weinberg

Měli byste navrhovat a navrhovat softwarová řešení s ohledem na udržovatelnost. Principy popsané v této části vám můžou pomoct při rozhodování o architektuře, která budou mít za následek čisté a udržovatelné aplikace. Obecně platí, že tyto principy vás povedou k vytváření aplikací z diskrétních komponent, které nejsou úzce propojené s jinými částmi aplikace, ale spíše komunikují prostřednictvím explicitních rozhraní nebo systémů zasílání zpráv.

Běžné principy návrhu

Oddělení zodpovědností

Hlavním principem při vývoji je oddělení obav. Tento princip tvrdí, že software by měl být oddělen na základě druhu práce, kterou provádí. Představte si například aplikaci, která obsahuje logiku pro identifikaci pozoruhodných položek, které se mají uživateli zobrazit, a naformátuje tyto položky určitým způsobem, aby byly výraznější. Chování zodpovědné za volbu položek, které se mají formátovat, by mělo být oddělené od chování zodpovědného za formátování položek, protože tyto aspekty jsou samostatné a jejich spojitost je pouze náhodná.

V architektuře lze aplikace logicky sestavit tak, aby dodržovaly tento princip oddělením základního obchodního chování od infrastruktury a logiky uživatelského rozhraní. Obchodní pravidla a logika by se v ideálním případě měly nacházet v samostatném projektu, který by neměl záviset na jiných projektech v aplikaci. Toto oddělení pomáhá zajistit, aby byl obchodní model snadno testovaný a mohl se vyvíjet, aniž by byl úzce svázán s podrobnostmi implementace nízké úrovně (pomáhá také v případě, že obavy z infrastruktury závisí na abstrakcích definovaných v obchodní vrstvě). Oddělení obav je klíčovým aspektem použití vrstev v aplikačních architekturách.

Zapouzdření

Různé části aplikace by měly použít zapouzdření k izolaci od ostatních částí aplikace. Komponenty a vrstvy aplikací by měly být schopny upravit jejich interní implementaci, aniž by došlo k narušení jejich spolupracovníků, pokud nejsou porušeny externí kontrakty. Správné použití zapouzdření pomáhá dosáhnout volné spojky a modularity v návrhech aplikací, protože objekty a balíčky lze nahradit alternativními implementacemi tak dlouho, dokud je zachováno stejné rozhraní.

Ve třídách je zapouzdření dosaženo omezením vnějšího přístupu k internímu stavu třídy. Pokud chce objekt mimo objekt manipulovat se stavem objektu, měl by to provést prostřednictvím dobře definované funkce (nebo vlastnosti setter) místo přímého přístupu k privátnímu stavu objektu. Stejně tak samotné komponenty aplikací a aplikace by měly vystavit dobře definovaná rozhraní pro své spolupracovníky, aby je mohli používat, místo aby jejich stav bylo možné upravovat přímo. Tento přístup uvolní interní návrh aplikace tak, aby se v průběhu času vyvíjel, aniž by se museli obávat, že tím dojde k narušení spolupracovníků, pokud jsou veřejné zakázky zachovány.

Proměnlivý globální stav je protichůdný k zapouzdření. Hodnotu načtenou z proměnlivého globálního stavu v jedné funkci nelze spoléhat na to, aby měla stejnou hodnotu v jiné funkci (nebo ještě dále ve stejné funkci). Pochopení obav z proměnlivého globálního stavu je jedním z důvodů, proč programovací jazyky, jako je C#, podporují různá pravidla rozsahu, která se používají všude, od příkazů přes metody až po třídy. Je vhodné si uvědomit, že architektury řízené daty, které se spoléhají na centrální databázi pro integraci v rámci aplikací a mezi aplikacemi, se samy rozhodují spoléhat na proměnlivý globální stav, který je reprezentován databází. Klíčovým aspektem návrhu řízeného doménou a čisté architektury je, jak zapouzdřovat přístup k datům a jak zajistit, aby stav aplikace nebyl neplatný přímým přístupem k jeho formátu trvalosti.

Inverze závislostí

Směr závislosti v rámci aplikace by měl být ve směru abstrakce, nikoli podrobností implementace. Většina aplikací je napsaná tak, aby toky závislostí v době kompilace ve směru spouštění modulu runtime vytvářely graf přímých závislostí. To znamená, že pokud třída A volá metodu třídy B a třídy B metodu třídy C, pak v době kompilace třída A bude záviset na třídě B a třída B bude záviset na třídě C, jak je znázorněno na obrázku 4-1.

Graf přímých závislostí

Obrázek 4–1 Graf přímých závislostí

Použití principu inverze závislostí umožňuje A volat metody na abstrakci, kterou B implementuje, což umožňuje A volat B během běhu programu, zatímco B závisí na rozhraní řízeném A v době kompilace (tedy invertování typické závislosti kompilace). V době běhu zůstává tok provádění programu beze změny, ale zavedení rozhraní znamená, že různé implementace těchto rozhraní lze snadno připojit.

Invertovaný graf závislostí

Obrázek 4–2 Invertovaný graf závislostí

Inverze závislostí je klíčovou součástí vytváření volně propojených aplikací, protože podrobnosti implementace lze zapsat, aby závisely na a implementovaly abstrakce vyšší úrovně, a ne naopak. Výsledné aplikace jsou testovatelné, modulární a udržovatelné v důsledku toho. Postup injektáže závislostí je možný pomocí inverze závislostí.

Explicitní závislosti

Metody a třídy by měly explicitně vyžadovat všechny spolupracující objekty, které potřebují ke správnému fungování. Označuje se jako Princip skutečných závislostí. Konstruktory tříd poskytují příležitost k identifikaci věcí, které potřebují, aby byly v platném stavu a aby správně fungovaly. Pokud definujete třídy, které lze instancovat a volat, ale které fungují pouze v případě, že jsou zavedeny určité globální nebo infrastrukturní komponenty, tyto třídy jsou vůči svým klientům neupřímné. Kontrakt konstruktoru říká klientovi, že potřebuje pouze zadané věci (pravděpodobně nic, pokud třída právě používá konstruktor bez parametrů), ale pak za běhu ukazuje, že objekt skutečně potřeboval něco jiného.

Dodržováním zásad explicitních závislostí jsou vaše třídy a metody otevřené vůči svým klientům ohledně toho, co potřebují k fungování. Díky principu je váš kód více zdokumentovaný a kódovací kontrakty uživatelsky přívětivější, protože uživatelé budou důvěřovat, pokud poskytují to, co je potřeba ve formě parametrů metody nebo konstruktoru, objekty, se kterými pracují, se budou chovat správně za běhu.

Jedna odpovědnost

Princip jediné odpovědnosti se vztahuje na objektově orientovaný návrh, ale lze jej považovat také za princip architektury podobný oddělení obav. Uvádí, že objekty by měly mít pouze jednu zodpovědnost a že by měly mít pouze jeden důvod ke změně. Konkrétně jediná situace, kdy by se objekt měl změnit, nastává tehdy, když je třeba aktualizovat způsob, jakým plní svou jedinou odpovědnost. Následující princip pomáhá vytvářet volně svázané a modulární systémy, protože mnoho druhů nového chování lze implementovat jako nové třídy, nikoli přidáním další odpovědnosti do stávajících tříd. Přidávání nových tříd je vždy bezpečnější než změna existujících tříd, protože na nových třídách ještě nezávisí žádný kód.

V monolitické aplikaci můžeme na vrstvy v aplikaci použít jediný princip odpovědnosti na vysoké úrovni. Odpovědnost za prezentaci by měla zůstat v projektu uživatelského rozhraní, zatímco odpovědnost za přístup k datům by měla být zachována v rámci projektu infrastruktury. Obchodní logika by se měla uchovávat v základním projektu aplikace, kde se dá snadno testovat a může se vyvíjet nezávisle na ostatních zodpovědnostech.

Pokud se tento princip použije na aplikační architekturu a přejde do jejího logického koncového bodu, získáte mikroslužby. Daná mikroslužba by měla mít jedinou zodpovědnost. Pokud potřebujete rozšířit chování systému, je obvykle lepší to udělat přidáním dalších mikroslužeb, nikoli přidáním odpovědnosti do existujícího systému.

Další informace o architektuře mikroslužeb

Vyvarujte se opakování (DRY)

Aplikace by se měla vyhnout určení chování souvisejícího s konkrétním konceptem na více místech, protože tento postup je častým zdrojem chyb. V určitém okamžiku bude změna požadavků vyžadovat změnu tohoto chování. Je pravděpodobné, že nejméně jedna instance chování se nepodaří aktualizovat a systém se bude chovat nekonzistentně.

Místo duplikování logiky ji zapouzdřte do programovací konstrukce. Vytvořte tuto konstrukci jako jedinou autoritu nad tímto chováním a zajistit, aby jakákoli jiná část aplikace, která toto chování vyžaduje, používala novou konstrukci.

Poznámka:

Vyhněte se spojování chování, které se opakuje pouze náhodou. Například vzhledem k tomu, že obě dvě různé konstanty mají stejnou hodnotu, neznamená to, že byste měli mít jenom jednu konstantu, pokud koncepčně odkazují na různé věci. Duplikace je vždy vhodnější než párování s nesprávnou abstrakcí.

Neznalost trvalosti

Neznalost trvalosti (PI) označuje typy, které je třeba uchovat, ale jejichž kód není ovlivněn volbou technologie trvalosti. Takové typy v .NET jsou někdy označovány jako prosté staré objekty CLR (POCOs), protože nemusí dědit z konkrétní základní třídy nebo implementovat konkrétní rozhraní. Ignorování perzistence je cenné, protože umožňuje udržovat stejný obchodní model více způsoby, což aplikaci poskytuje dodatečnou flexibilitu. Volby trvalosti se můžou v průběhu času měnit z jedné databázové technologie na jinou nebo mohou být vyžadovány další formy trvalosti kromě toho, s čím aplikace začala (například použití mezipaměti Redis nebo azure Cosmos DB kromě relační databáze).

Mezi příklady porušení tohoto principu patří:

  • Požadovaná základní třída.

  • Požadovaná implementace rozhraní.

  • Třídy zodpovědné za ukládání (například model aktivního záznamu).

  • Povinný konstruktor bez parametrů.

  • Vlastnosti vyžadující virtuální klíčové slovo

  • Požadované atributy specifické pro trvalost

Požadavek, aby třídy měly některou z výše uvedených vlastností nebo chování, přidává spojení mezi typy, které mají být zachovány, a volbu technologie trvalosti, což ztěžuje přijetí nových strategií přístupu k datům v budoucnu.

Ohraničené kontexty

Ohraničené kontexty představují centrální vzor v návrhu Domain-Driven. Poskytují způsob řešení složitosti ve velkých aplikacích nebo organizacích tím, že je rozdělí do samostatných koncepčních modulů. Každý koncepční modul pak představuje kontext, který je oddělený od ostatních kontextů (tedy ohraničený) a může se vyvíjet nezávisle. Každý ohraničený kontext by měl být v ideálním případě volný pro výběr vlastních názvů pro koncepty v něm a měl by mít výhradní přístup k vlastnímu úložišti trvalosti.

Minimálně by se jednotlivé webové aplikace měly snažit být vlastním ohraničeným kontextem s vlastním úložištěm trvalosti pro svůj obchodní model, a ne sdílet databázi s jinými aplikacemi. Komunikace mezi ohraničenými kontexty probíhá prostřednictvím programových rozhraní, nikoli prostřednictvím sdílené databáze, která umožňuje provádět obchodní logiku a události v reakci na provedené změny. Ohraničené kontexty úzce souvisejí s mikroslužbami, které jsou také ideálně realizovány jako svoje vlastní samostatné ohraničené kontexty.

Dodatečné zdroje