Zásady architektury
Tip
Tento obsah je výňatek z eBooku, architekta moderních webových aplikací s ASP.NET Core a Azure, který je k dispozici na webu .NET Docs nebo jako bezplatný soubor PDF ke stažení, který si můžete přečíst offline.
"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í obav
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 toto chování jsou samostatné obavy, které se shodují pouze s druhým.
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 antitetikální 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 oborů, která se používají všude od příkazů po 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, jsou samy o sobě v závislosti na proměnlivém globálním stavu reprezentované 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.
Obrázek 4–1 Graf přímých závislostí
Použití inverze závislostí umožňuje A volat metody na abstrakci, kterou B implementuje, což umožňuje A volat B za běhu, ale aby B závisel na rozhraní řízeném 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.
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 objekty, které potřebují ke správnému fungování. Označuje se jako explicitní závislosti. 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 vytvořit a volat, ale to bude fungovat pouze v případě, že jsou některé globální nebo infrastrukturní komponenty zavedeny, tyto třídy jsou pro své klienty nepočestné . 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.
Pomocí zásad explicitních závislostí jsou vaše třídy a metody upřímné se svými klienty o tom, 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ě je jediná situace, ve které by se objekt měl změnit, je-li způsob, jakým provádí svou jednu odpovědnost, musí být aktualizována. 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
Nezopakujte se (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 konstruktoru jako jedinou autoritu nad tímto chováním a mít jakoukoli jinou část aplikace, která toto chování vyžaduje použití nového konstruktoru.
Poznámka:
Vyhněte se společnému chování, které se shodou okolností opakuje. 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í.
Stálost
Trvalostnosti ( PI) odkazuje na typy, které je třeba zachovat, ale jejichž kód není ovlivněn výběrem 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í. Trvalost je cenná, protože umožňuje zachování stejného obchodního modelu několika způsoby a nabízí aplikaci větší 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 jsou centrálním vzorem v návrhu řízeném doménou. 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 se mapují úzce na mikroslužby, které jsou také ideálně implementovány jako jejich vlastní jednotlivé ohraničené kontexty.