Slovník pojmů
Hexagonální architektura
Aplikace je střed systému; její okolí jsou vyměnitelné způsoby, jak do ní vstoupit nebo z ní získat výsledek.
Stručná definice
Port je kontrakt aplikace, adaptér jej překládá pro konkrétní okolí.
Hexagonální architektura neboli Ports and Adapters popisuje aplikaci jako jádro obklopené rozhraními. Vstupní adaptér přijme podnět z okolí a zavolá vstupní port. Výstupní port naopak vyjadřuje schopnost, kterou jádro potřebuje od okolního systému; jeho konkrétní implementaci dodá výstupní adaptér.
Název neznamená šest vrstev ani síťový TCP port. Hexagon je jen kresba, která má zabránit představě, že aplikace musí mít jeden privilegovaný vstup, například HTTP controller. Jediný případ použití může obsloužit web, příkazovou řádku, message consumer i test, aniž by se jeho pravidla kopírovala do každého z nich.
Jaký problém řeší
Technický vstup ani dodavatel služby nemá určovat pravidla případu použití.
Přístup je přínosný, když tentýž aplikační scénář potřebuje více vstupů, více implementací nebo nezávislé testování.
- HTTP endpoint, CLI import a message consumer nad stejnou aplikační operací
- výběr mezi HTTP API, XML feedem a připravenými testovacími daty pro jeden zdroj informací
- oddělení úlohy aplikace od konkrétního databázového klienta nebo fronty zpráv
- rychlé testy případů použití bez sítě, souborového systému a skutečné databáze
- postupná výměna externího poskytovatele bez přepsání míst, která používají jeho data
- sjednocení významu operace napříč interním nástrojem, veřejným API a dávkovým zpracováním
Praktický příklad
Získání výdejních míst dopravce
Aplikace potřebuje pro checkout vrátit výdejní místa zvoleného dopravce. Vstupní port ListCarrierPoints vyjadřuje operaci dostupnou okolí aplikace. HTTP controller jej může zavolat po webovém požadavku, ale stejný port může obsloužit také interní CLI nástroj pro kontrolu dostupnosti.
Jádro si pro načtení dat vyžádá výstupní kontrakt CarrierPointSource. Produkce může mít HTTP API adaptér, starší integrace XML adaptér a test jednoduchý fake s předem připravenými body. Nejde o to skrýt každý objekt za rozhraní; kontrakt má smysl právě proto, že zdroj se může lišit a aplikace potřebuje stabilní význam výsledku.
Textový diagram
HTTP controller / CLI příkaz
→ ListCarrierPoints (vstupní port a use case)
→ CarrierPointSource (výstupní port)
→ HTTP API adaptér | XML adaptér | testovací fake
→ seznam výdejních míst pro aplikaci
Jak funguje
Dvě strany rozhraní kolem aplikačního jádra
Textový diagram je alternativou k hexagonu: několik různých adaptérů může vstoupit stejným portem a několik jiných může obsloužit potřebu jádra.
- Vstupní adaptér HTTP controller, CLI příkaz nebo consumer zprávy převede konkrétní transport na volání aplikace. Validuje technický tvar vstupu a předá mu srozumitelná data.
- Vstupní port Port popisuje, co může okolí po aplikaci chtít. Může mít podobu rozhraní, veřejné aplikační služby nebo jiného explicitního kontraktu; není to síťový port ani automaticky jedna třída.
- Aplikační jádro Případ použití koordinuje pravidla, rozhodne o výsledku a zná význam operace. Nemá být svázán s tím, zda jej spustilo HTTP, plánovač nebo příjem zprávy.
- Výstupní port Jádro pojmenuje schopnost, kterou potřebuje od okolí: načíst data, uložit změnu, odeslat příkaz nebo zjistit čas. Kontrakt má vyjadřovat potřebu aplikace, ne kopírovat API dodavatele.
- Výstupní adaptér Databázový klient, HTTP integrace, souborové úložiště či fake přeloží kontrakt do konkrétní technologie. Zvolená implementace se zapojí při sestavení aplikace.
Hlavní části
Porty a adaptéry mají odlišnou odpovědnost.
Rozdělení není předpis pro počet souborů. Je to způsob, jak poznat, které rozhodnutí patří aplikaci a které konkrétní technologii.
Inbound port
Vstupní port dává okolí stabilní způsob, jak vyvolat případ použití. Jeho kontrakt popisuje operaci aplikace, například vytvořit objednávku nebo vypsat výdejní místa.
Inbound adapter
Vstupní adaptér připojuje konkrétní kanál. Controller rozumí HTTP statusům a formulářovým datům, CLI adaptér argumentům a consumer formátu zprávy; tyto detaily nepatří do jádra.
Outbound port
Výstupní port je kontrakt definovaný z pohledu potřeby aplikace. CarrierPointSource nemusí prozrazovat, zda za ním je REST, XML nebo databáze, pokud tyto rozdíly nejsou významné pro use case.
Outbound adapter
Výstupní adaptér implementuje port přes konkrétní systém. Může obsahovat autentizaci, převod formátu, retry nebo mapování chyb dodavatele na význam, kterému jádro rozumí.
Sestavení aplikace
Místo, kde se vybere konkrétní adaptér, patří na okraj systému. Předání závislostí do use casu umožní zaměnit implementaci v testu i při provozní změně.
Vztah k podobným přístupům
Porty nejsou vrstvy ani mikroslužby.
Přístupy se mohou překrývat, ale není vhodné z nich dělat jeden univerzální recept.
- Hexagonální a Clean Architecture
- Oba směry chrání aplikační pravidla před technickými detaily. Hexagonální architektura je zvlášť užitečná pro rozlišení vstupních a výstupních adaptérů, kdežto Clean Architecture akcentuje dependency rule a soustředné oblasti odpovědností.
- Port a adaptér
- Port je kontrakt na hranici aplikace. Adaptér je kód, který tento kontrakt překládá pro konkrétní technologii. Jeden adaptér může tvořit více tříd a jeden port může mít více adaptérů.
- Hexagonální architektura a DDD
- Ports and Adapters organizuje kontakt aplikace s okolím. Domain-Driven Design pomáhá modelovat problémovou doménu a její jazyk. Lze je spojit, ale aplikace může mít porty i bez bohatého doménového modelu.
- Kontrakt a dependency injection
- Kontrakt určuje, jakou schopnost aplikační část potřebuje. Dependency injection je technika, jak vybraný adaptér předat při vytváření objektu. Samotné předání závislosti ještě neříká, zda kontrakt dává smysl.
Výhody a omezení
Výměnnost je přínos jen tam, kde se skutečně očekává změna.
Přínosy
- jeden případ použití lze využít z více technologických vstupů
- externí integrace a databáze mají zřetelnější místo na okraji aplikace
- testy mohou pracovat s řízenými adaptéry bez skutečné infrastruktury
- překlad chyb a formátů dodavatele je soustředěný mimo aplikační pravidla
Omezení a časté chyby
- vytvářet port pro každou interní třídu, i když nemá alternativu ani hranici
- kopírovat detailní DTO externího API do celého aplikačního modelu
- považovat adapter za jednu povinnou třídu nebo port za síťový TCP port
- předpokládat, že přístup vyžaduje mikroservisy, mnoho vrstev nebo oddělené nasazení
Pragmatičnost
Přímá služba může být lepší než abstrakce bez důvodu.
Pokud jednoduchá administrační akce volá jediný stabilní frameworkový komponent a nebude mít jiný vstup ani implementaci, další port a adaptér mohou jen prodloužit cestu k výsledku. Přímé použití dobře zapouzdřené služby je v takovém případě legitimní. Složitost se vyplatí přidat ve chvíli, kdy různé zdroje, vstupy nebo testy skutečně potřebují oddělený význam.
Dobrou hranici lze poznat podle toho, že ji umí tým popsat větou z jazyka aplikace: například „potřebujeme zdroj výdejních míst“, nikoli „potřebujeme wrapper nad klientem dodavatele“. Pokud kontrakt jen opakuje všechna volání jedné knihovny, jádro pravděpodobně chráněné není a přibylo pouze další rozhraní.
Na co myslet
Hranice má přeložit význam, ne jen přemístit frameworkové API.
Konkrétní adaptér může být složitý, ale jeho složitost nemá bezdůvodně protékat do případu použití.
- pojmenovat port podle schopnosti, kterou aplikace opravdu potřebuje
- udržet HTTP, CLI a formát zpráv ve vstupních adaptérech
- převádět chyby a datové formáty dodavatele na chování srozumitelné aplikaci
- používat testovací fake pro rozhodování a samostatně ověřit produkční adaptér proti jeho hranici
- přidávat nové porty až pro skutečné zdroje změny, ne jako mechanickou konvenci
Časté otázky
Hexagonální architektura v praxi
Je port v hexagonální architektuře síťový port?
Ne. Jde o aplikační kontrakt na hranici systému. Může být vyjádřen rozhraním, metodou aplikační služby nebo jiným explicitním způsobem, ale nesouvisí s TCP či UDP portem.
Musí být každý adaptér jedna třída?
Ne. Adaptér je odpovědnost překladu mezi aplikací a konkrétním okolím. V praxi může obsahovat klienta, mapper, konfiguraci i několik tříd; malý adaptér může být naopak jediná služba.
Vyžaduje hexagonální architektura mikroslužby?
Ne. Hodí se i pro jeden modul modulárního monolitu nebo pro běžnou webovou aplikaci. Neříká nic o počtu nasazovaných služeb, pouze o hranici mezi aplikačním jádrem a okolím.
Kdy je vhodné port nepřidávat?
Když neexistuje samostatná aplikační hranice ani pravděpodobná změna vstupu či výstupu. Pokud kontrakt pouze obaluje jednu stabilní interní třídu bez jiného významu, zpravidla nepřináší hodnotu.
Jak architekturu používám v praxi
Porty volím pro místa, kde technologie nemá řídit význam aplikace.
Při integracích a více vstupních bodech odděluji aplikační rozhodnutí od konkrétního HTTP, databáze nebo fronty. Rozsah hranice přitom volím podle skutečné složitosti.