Kapitola 02 · Základy · Subdomény: Core, Supporting, Generic

Subdomény: Core, Supporting, Generic

Než vznikne první agregát, je potřeba vědět, kde se DDD vůbec vyplatí. Rozdělení na Core, Supporting a Generic subdomény určuje, kolik úsilí, jak zkušené lidi a jakou technologii si která část aplikace zaslouží.

Autor M. Katuščák
Doba čtení ≈ 39 min
Náročnost mírně pokročilá
Publikováno · Aktualizováno ·
Obsah kapitoly

Kapitola ve videokurzu

Celý videokurz
  1. Kapitoly videa a přepis dílu 02a →
  2. Kapitoly videa a přepis dílu 02b →

Přehrávač YouTube se načte až po kliknutí.

02.01 Proč subdomény předcházejí všemu ostatnímu#

Vývojářský reflex „naimplementuju to celé pořádně“ je drahý a u většiny produktů marný. Ne každá část aplikace si zaslouží stejnou hloubku modelování. Kdo modeluje všechno stejně pečlivě, vyčerpá rozpočet dřív, než dojde k tomu, co zákazníka skutečně zajímá. Evans v Domain-Driven Design (2003), kapitola „Distillation“, zavádí pro celou doménu strategický filtr a dva jeho pojmy: Core Domain a Generic Subdomains [1]. Vaughn Vernon v Implementing Domain-Driven Design (2013) trojici dokončil pojmem Supporting Subdomain [2]. Evans slovo „supporting“ v roce 2003 používá, ale jen popisně; jako pojmenovaný vzor v knize ani v DDD Reference (2015) nefiguruje. Než napíšete první Aggregate nebo Value Object, potřebujete vědět, která část domény je vaše konkurenční výhoda, která je jen nutná a kterou nemá smysl vůbec psát. Dnes se tomuto filtru říká rozdělení domény na Core, Supporting a Generic subdomény.

Obchodní myšlenka je přitom starší než DDD. Geoffrey Moore v Living on the Fault Line (2000) dělí činnosti firmy na core, které ji odlišují, a context, tedy všechno ostatní, co firma dělat musí, aby zůstala na trhu. V Dealing with Darwin (2005) z toho vyvodil pravidlo: context minimalizovat, automatizovat nebo outsourcovat. Evansova destilace říká totéž jazykem modelu.

Subdoména není totéž co Bounded Context, ačkoliv se oba pojmy v rozhovorech běžně zaměňují. Bounded Context je implementační hranice: místo, kde platí jeden Ubiquitous Language, jeden konzistentní model a typicky jeden tým. Zda běží jako samostatná služba, nebo jako modul monolitu, je provozní rozhodnutí. Subdoména ohraničuje obchod. Je to kus problému, který organizace řeší jako jednu ucelenou kapitolu. Vztah mezi nimi není 1:1. Jedna subdoména („Pricing“) se může rozdělit do více BC: Catalog počítá indikativní cenu, Checkout závaznou cenu se slevami. Naopak jeden BC může pokrývat více malých subdomén, například Backoffice zpravidla sdruží kousky reportingu, fakturace i správy uživatelů.

Dělicí čára mezi obojím není ostrá. Zkratka „subdomény objevuje byznys, kontexty navrhují inženýři“ se dobře pamatuje, ale neplatí přesně: hranice subdomény se často vyjasní až ve chvíli, kdy tým začne modelovat.

Vernon to v kapitole 2 Implementing Domain-Driven Design (2013) formuluje pragmaticky [2]: doména je celý problémový prostor organizace; subdoména je jeho logická část; Bounded Context je řešení, které pro ni navrhujete. Vlad Khononov v Learning Domain-Driven Design (O'Reilly 2021) klasifikací subdomén celý výklad otevírá, v kapitole 1 „Analyzing Business Domains“ [3]. Je to zároveň nejlevnější nástroj DDD: stojí jediný workshop a může přerozdělit rozpočet v milionech korun.

Chyba ve volbě subdomény stojí násobně víc než chyba v jednotlivém Aggregate, a proto úvodní kapitola řadí strategický design před taktický. Špatně navržený Aggregate se refaktoruje za dva sprinty. Špatně klasifikovaná Core Domain znamená rok vývoje v nesprávné oblasti a rok, který chyběl skutečnému diferenciátoru. Filtrovat se proto má dřív, než se modeluje.

02.02 Tři kategorie subdomén#

Rozdělení je záměrně hrubé: tři škatulky, žádné odstíny. Jakmile padne rozhodnutí o investici, podrobnosti už řeší taktická úroveň (Aggregate, Repository, Domain Service). Strategické úrovni stačí tolik granularity, aby šlo říct: do této kategorie investujeme, do této ne.

Core Domain (jádrová doména)

Část domény, která tvoří konkurenční výhodu organizace, tedy to, kvůli čemu zákazníci platí právě vám a ne někomu jinému. Test: „pokud z toho zítra ustoupíme, ztratíme zákazníky.“ Opačně formulováno: kdyby šla stejná funkcionalita stejně levně koupit od dodavatele, není to Core, ale Generic.

Důsledky pro tým a stack: plný taktický design (Aggregate, Value Object, Domain Event), seniorní tým, vlastní IP, nízká tolerance k externím závislostem v jádře. Sem patří i nejvíc automatizovaných testů, nejpřísnější code review a nejčastější diskuse s doménovými experty. Khononov uvádí jako příklady [3] ridesharing a matching jezdců u Uberu nebo ranking algoritmus vyhledávání u Googlu. Obojí je pro svou firmu Core, protože právě tím se liší od trhu. Khononov přidává i druhé kritérium, které samotná konkurenční výhoda neobsáhne: Core subdoména s nízkou složitostí poskytne jen krátkodobou výhodu, protože ji konkurence dorovná. Core subdomény jsou proto z podstaty složité.

Supporting Subdomain (podpůrná subdoména)

Část domény, která je nezbytná pro provoz, ale nediferencuje vás. Test: „potřebujeme to, ale nikdo nás kvůli tomu nenajme.“ Klasické příklady: správa objednávek v e-shopu, evidence skladu, fakturace, reporting pro management. Kdyby Supporting fungoval „stejně jako u konkurence“, nikdo by si toho nevšiml. Kdyby ale vůbec nefungoval, provoz by stál.

Důsledky pro tým a stack: lehké DDD (často stačí Transaction Script nebo CRUD nad Doctrine entitami), juniorní až mediorní tým, ochota sáhnout po hotových řešeních, kde to jde. Cílem je fungovat spolehlivě s minimálními náklady na údržbu, ne mít nejhezčí model. Místo paušálu se hodí test. U Supporting subdomény, kterou nelze pořídit hotovou jako Generic, se taktický návrh vyplatí za tří podmínek: tým ho zvládá, model je inovativní a má vydržet roky. Kde tyto podmínky neplatí, vynaloží organizace seniorní čas na něco, co nikoho nezajímá. Test je autorská konstrukce této knihy; Vernon [2] dává obecnější vodítko, že Supporting si zaslouží méně modelovacího úsilí než Core.

Generic Subdomain (generická subdoména)

Část domény, která je komoditizovaná. Test: „řešení existuje 30 let, prodává se v krabici nebo v cloudu, koupíme.“ Klasické příklady: autentizace uživatelů, posílání transakčních e-mailů, integrace platební brány, generování PDF faktur, fulltext, antispam. Výchozí volbou je koupit, ne psát. Evans je v tomto opatrnější, než se mu obvykle přisuzuje: in-house implementaci uvádí mezi čtyřmi přípustnými variantami sourcingu a o hotových řešeních píše, že se obvykle nevyplatí, ale stojí za prozkoumání [1]. Vlastní kód je tedy obhajitelný tam, kde integrační náklad převýší ten implementační. Kde takový důvod chybí, je vlastní kód znovuobjevováním kola na účet Core Domain.

Důsledky pro tým a stack: SaaS, open-source knihovna, externí API, případně tenký bridge / Anti-Corruption Layer mezi naším modelem a komoditním řešením. Sem patří integrace s Auth0 / Keycloak, Stripe, Mailgun, AWS SES, Algolia. Velikostní pravidlo palce: pokud konkrétní Generic subdoména spotřebuje víc než 5–10 % vývojové kapacity, je buď zvolený produkt nevhodný, nebo subdoména špatně klasifikovaná. Číslo je autorské, žádný primární zdroj ho neuvádí – stejně jako ostatní procenta v této kapitole.

FIG. 02.2-A E-shop: subdoménové členění a investice

Tři osy, ne jedna

Konkurenční výhoda je jen první osa. Khononov přidává další dvě, komplexitu a volatilitu, a teprve celá trojice odliší Generic od Supporting jinak než větou „existuje na to SaaS“ [3].

Typ Konkurenční výhoda Komplexita Volatilita Sourcing
Core ano vysoká vysoká build in-house
Supporting ne nízká nízká build in-house nebo outsource
Generic ne vysoká nízká koupit / převzít hotové

Core i Generic jsou složité. Rozdíl je v tom, že Generic je složitý vyřešený problém, který se nemění: kryptografie, OAuth, doručitelnost e-mailů. Supporting subdoména je naproti tomu jednoduchá z podstaty: formuláře, seznamy, dva tři stavy. Označení „glorified CRUD“ na ni sedí.

Z tabulky plyne diagnostická otázka, kterou klasifikace podle jediné osy položit neumí: složitost ve Supporting subdoméně je signál. Buď se v ní skrývá nerozpoznaná Core subdoména, nebo jde o nahodilou složitost, kterou tam nikdo nechtěl. Nick Tune pro kombinaci vysoké složitosti a nulového odlišení používá název Suspect Supporting [6]. V legacy projektech bývá nejčastějším nálezem celého cvičení.

Volatilita je třetí osa a rozhoduje o architektuře. Pravidla, která se mění každý sprint, potřebují model, jenž změnu unese; pravidla, která se nezměnila deset let, si vystačí s tabulkou a jedním servisním objektem. Khononovův rozhodovací řetězec (typ subdomény → vzor obchodní logiky → architektonický styl) rozvádí kapitola o tom, kdy DDD nepoužívat.

Distillation nad rámec klasifikace

Klasifikace Core / Supporting / Generic je první krok Evansovy destilace, ne celý postup. Kapitola 15 „Distillation“ v Domain-Driven Design obsahuje sedm vzorů: CORE DOMAIN, GENERIC SUBDOMAINS, DOMAIN VISION STATEMENT, HIGHLIGHTED CORE, COHESIVE MECHANISMS, SEGREGATED CORE a ABSTRACT CORE. Domain Vision Statement rozebírá sekce 02.09. Abstract Core je z celé sady nejdražší: vytáhne nejobecnější doménové koncepty do samostatného modulu a vyplatí se až u modelu, který tým čtvrtým rokem nestíhá číst. Po zbývajících třech technikách se sahá nejdřív:

Highlighted Core. Jádrové prvky se označí přímo v existujícím modelu: krátkým destilačním dokumentem (pár stran) nebo zvýrazněním v dokumentaci a kódu, bez refaktoringu. Je to nejlevnější technika destilace. Hodí se, když tým potřebuje sdílené vědomí o tom, co je jádro, ale na strukturální změny nemá čas ani mandát.

Segregated Core. Refaktoring, který jádrové koncepty přesune do samostatného modulu a odřízne je od podpůrného kódu. Vyplatí se, když je Core tak propletené se Supporting třídami, že ho v modelu nikdo nevidí a každá změna jádra táhne za sebou periferii. Platí se přepisem a investice se vrací jen u skutečné Core Domain.

Cohesive Mechanisms. Výpočetně složitý, ale koncepčně oddělitelný mechanismus (grafový algoritmus, přepočtový engine) se vyčlení do samostatného pomocného frameworku. Doménový model pak deklaruje, co se počítá; mechanismus řeší jak. Nasazuje se ve chvíli, kdy technické „jak“ začíná v modelu zastiňovat doménové „co“.

02.03 Jak rozpoznat Core Domain – pětibodový test#

Rozpoznat Core Domain je nejtěžší krok. Týmy mají sklon prohlašovat všechno za „strategicky důležité“ a pojem Core Domain tím klesne na prázdný štítek.

Následující pětibodový test je autorská konstrukce této knihy, ne heuristika převzatá z primárního zdroje. Khononov odlišuje typy subdomén třemi osami: konkurenční výhoda, složitost byznys logiky a volatilita [3]. Ke svým dvěma osám nabízí ddd-crew katalog devíti otázek [5]. Test níže obojí zjednodušuje do rychlého filtru, který zvládne jednotlivec u kávy. Každou položku ohodnoťte ANO/NE. Tři a více ANO znamená kandidáta na Core Domain; v opačném případě jde o Supporting nebo Generic.

  1. „Pokud bychom to outsourcovali, přijdeme o hlavní produkt?“

    Pokud ANO (= bez té funkcionality nemáme co prodávat) → jde o kandidáta na Core. Pokud NE → není to Core. Příklad: e-shop může outsourcovat platby (není Core), ale nemůže outsourcovat svůj sortiment a způsob jeho doporučování (kandidát na Core).

  2. „Chybí pro tuto oblast tržní benchmark / standard?“

    Pokud NE (standard existuje) → s vysokou pravděpodobností Generic. Standard znamená, že problém už někdo vyřešil a trh se shodl, jak má řešení vypadat. Příklad: OAuth 2.0 / OpenID Connect pro autentizaci, ISO 8583 pro karetní platby, RFC 5321 pro SMTP. Pokud ANO, výsledek je neutrální: může jít o Core i Supporting.

  3. „Píšeme to už podruhé jinak než konkurence?“

    Pokud ANO → silný indikátor Core. Vývoj „jinak než ostatní“ je nákladný a vyplatí se jen tehdy, když z té odlišnosti plyne tržní výhoda. Pokud děláme něco jinak bez hmatatelné výhody, jde často o špatně klasifikovanou subdoménu, pro kterou se mělo koupit standardní řešení.

  4. „Mluví o tom CEO / VP product každý týden?“

    Pokud ANO → silný indikátor Core. Vrcholný management se nezabývá Supporting subdoménami; o těch slyší jen tehdy, když přestanou fungovat. Když o určité funkcionalitě průběžně rozhoduje CEO, jde nejspíš o konkurenční diferenciátor. Když ne, jde spíš o provoz.

  5. „Plánujeme v této oblasti experimentovat / měnit pravidla často?“

    Pokud ANO → indikátor Core. Frekvence změn prozrazuje, jak silně se v té oblasti hraje o trh. V Generic subdoménách se pravidla nemění. Autentizace funguje letos stejně jako loni. V Core Domain tým testuje, A/B měří a iteruje na doménových pravidlech, protože právě v iteraci je výhoda.

Test má jeden užitečný vedlejší efekt: nutí formulovat obchodní důvody před technickými. Pokud na otázku 4 („mluví o tom CEO?“) tým odpoví „nevím, neptali jsme se“, je to znamení, že strategický rozhovor musí proběhnout ještě před začátkem implementace.

Core Domain Charts – tři škatulky jako souvislá rovina

Pětibodový test dá odpověď ANO/NE. Core Domain Charts komunity ddd-crew místo toho kreslí graf o dvou osách: svislá je komplexita, vodorovná obchodní diferenciace [5]. Subdomény se do něj umisťují jako body, ne jako položky ve třech přihrádkách. Core, Supporting a Generic pak nejsou škatulky, ale oblasti v jedné rovině. Subdoména, která leží mezi nimi, přestane být problémem klasifikace a stane se tématem k rozhovoru.

Právě v tom rozhovoru leží podle autorů hlavní přínos techniky. Komplexitu umí odhadnout inženýři, diferenciaci dodává produkt nebo byznys; graf je jediné místo, kde obě skupiny musí své odhady postavit vedle sebe. Šablona k tomu nabízí katalog otázek pro obě osy. U komplexity odděluje esenciální doménovou složitost od nahodilé technické a operační. U diferenciace se ptá zvlášť na obtížnost pro nováčka na trhu a pro stávajícího konkurenta.

Praktické rozdělení rolí: test v předchozí sekci jako rychlý filtr pro jednotlivce, chart jako výstup workshopu, který si tým pověsí na zeď a za rok do něj překreslí posuny.

02.04 Anti-vzor: „všechno je Core“#

Nejčastější chyba ve strategickém DDD není špatně navržený Aggregate, ale „všechno je Core“. Sklon k ní je psychologický. Každý vývojář, kterého se zeptáte, jestli je jeho oblast strategická, řekne ANO. Důvody: ego, kariérní obavy z „nedůležité“ oblasti a obecná tendence přeceňovat vlastní práci. Každá funkcionalita má svého hrdinu, který ji obhajuje jako nezbytnou pro firmu. Na workshopech Event Stormingu je ten jev dobře vidět [4].

Manažerská rovina situaci zhoršuje. Ředitel bez technického zázemí slyší od každého vedoucího týmu, že jeho oblast je strategická. Nemá nástroj, jak vyhodnotit, kde je investice opodstatněná a kde jde o obhajobu pozic. Rozpočet se pak rozteče rovnoměrně a Core dostane stejně jako fakturace. Menší konkurent, který do svého skutečného Core soustředí pětinásobek, takovou firmu předběhne.

Třetí rozměr je technický. Pokud je „všechno Core“, vznikne monolitický doménový model bez priorit: každá entita je prvotřídní, každý use case má vlastní Aggregate, každá akce má Domain Event. Refaktoring jednoho zákoutí se dotýká dvaceti dalších, výkon trpí, testy běží hodinu. Zdravá DDD aplikace má naopak ostře vyhraněnou hierarchii: pár Aggregatů v Core, lehké modely v Supporting a tenké adaptéry v Generic.

Obrana proti anti-vzoru „všechno je Core“ je přímočará: vynuťte si rozpočet. Před začátkem každého kvartálu (nebo OKR cyklu) si nakreslete tři škatulky: Core / Supporting / Generic. Do každé napište procentní podíl celkové vývojové kapacity. Pokud do Core spadne 80 %, klasifikace selhala. Distribuce, se kterou u průměrné B2B SaaS firmy počítá tato kniha: 20–30 % Core, 50–60 % Supporting, 10–20 % Generic. Jde o pravidlo palce zkalibrované na středně velkých produktech, ne o měřená data. Poslední číslo bývá nejnižší – Generic se z definice nepíše, jen integruje.

02.05 Mapování subdomén na Bounded Contexts#

Subdoména a Bounded Context se mapují přes tři standardní vztahy: 1:1 (jedna subdoména = jeden BC, žádoucí stav), 1:N (jedna subdoména se dělí do více BC) a N:1 (více malých subdomén žije v jednom BC, obvyklé pro Supporting / Generic). Vernon doporučuje cílit na 1:1 všude, kde to jde. Khononov jde dál a před rozdělováním souvislé funkcionality varuje: kontexty pak nelze rozvíjet nezávisle, protože tatáž změna požadavků zasáhne oba a vynutí si současné nasazení. Oprávněným důvodem k rozdělení zůstává potřeba oddělit vývojové cykly nebo škálovat část nezávisle na zbytku; to už je ale doporučení této knihy, ne jeho formulace [3].

Pro názornost imaginární e-shop střední velikosti (3–4 týmy, 25 vývojářů) rozložený na subdomény a Bounded Contexts:

Subdoména Klasifikace Bounded Context(s) Vztah Poznámka
Pricing & Promotions Core Catalog BC, Checkout BC 1:N Rozdělené kvůli provozu: listing a checkout se škálují i nasazují jiným tempem.
Personalized Recommendations Core Recommendation BC 1:1 Vlastní ML model + read-only projekce nákupů z ostatních BC.
Order Management Supporting Ordering BC 1:1 Jednoznačná hranice, lehké DDD.
Inventory Supporting Warehouse BC 1:1 Stav skladu, rezervace, příjemky.
Customer Support Supporting Support BC (Zendesk + ACL) 1:1 přes ACL Hotový SaaS, Anti-Corruption Layer kvůli mapování ID a zákazníků.
Identity / Auth Generic External IdP (Auth0) 1:1 přes ACL Žádný interní BC, jen tenký bridge a UserProvider.
Payments Generic External (Stripe / Adyen) 1:1 přes ACL Webhook subscriber, mapování na náš PaymentIntent.
Email Delivery Generic External (AWS SES / Mailgun) 1:1 přes ACL Symfony Mailer + transport bundle.
Reporting / Analytics Supporting Analytics BC N:1 Více malých subdomén (Sales, Stock, Marketing) sdílí jeden BC s read modely.

Tabulka ilustruje typický rozklad: Core má vlastní silně modelované BC, Supporting má 1:1 BC s lehčím designem, Generic přebírá cizí BC (externího providera) přes Anti-Corruption Layer. Radikálně jiné rozložení (např. 5 Core BC a žádný Generic) je signál, že klasifikaci je potřeba znovu ověřit. Kdo hranice teprve hledá, začne u Big Picture Event Stormingu; komu už hranice sedí a řeší, který tým co dostane, pokračuje kapitolou o Team Topologies.

02.06 Subdomény v Symfony – co to znamená pro strukturu projektu#

Symfony 8 dává plnou volnost v adresářové struktuře pod src/. Výchozí dělení src/Controller/, src/Entity/, src/Repository/ je technické: řadí kód podle vrstev. Pro DDD aplikaci je to chyba, protože na první pohled nejde poznat, do které subdomény funkcionalita patří. Junior, který hledá „jak se počítá cena“, musí projít všechny tři adresáře. Lepší cesta: strukturovat src/ primárně podle subdomén, sekundárně podle vrstev uvnitř subdomény.

Konkrétní rozložení v Symfony 8 e-shopu:

text src/ (struktura projektu)
1src/2├── Core/3│   ├── Pricing/                       ← plný DDD: Aggregate, VO, Domain Event4│   │   ├── Domain/5│   │   │   ├── Aggregate/Pricelist.php6│   │   │   ├── ValueObject/PriceRule.php7│   │   │   ├── Event/PricelistCreated.php8│   │   │   ├── Event/PricelistChanged.php9│   │   │   ├── Exception/ConflictingPriceRuleException.php10│   │   │   └── Repository/PricelistRepository.php          (interface)11│   │   ├── Application/12│   │   │   ├── Command/UpdatePrice.php13│   │   │   └── Handler/UpdatePriceHandler.php14│   │   └── Infrastructure/15│   │       ├── Doctrine/DoctrinePricelistRepository.php16│   │       └── Symfony/PricingMessageHandler.php17│   └── Recommendations/               ← analogická struktura18│19├── Supporting/20│   ├── Ordering/                      ← lehké DDD: minimal Aggregate, Doctrine ORM21│   │   ├── Domain/22│   │   │   ├── Order.php                                   (Doctrine entity + chování)23│   │   │   └── OrderRepository.php                         (interface)24│   │   ├── Application/25│   │   │   └── PlaceOrderHandler.php26│   │   └── Infrastructure/27│   │       └── Repository/DoctrineOrderRepository.php28│   ├── Inventory/29│   └── Reporting/30│31└── Generic/32    ├── Auth/                          ← bridge na Auth0, žádný custom Aggregate33    │   ├── Adapter/Auth0Client.php34    │   ├── Adapter/Auth0UserProvider.php35    │   └── Adapter/Auth0AuthenticationListener.php36    ├── Mail/37    │   └── Adapter/SesMailerAdapter.php38    └── Payment/39        └── Adapter/StripeWebhookHandler.php

Strukturální rozdíl odráží rozdíl strategický: Core má tři vrstvy (Domain / Application / Infrastructure), Supporting také tři, ale tenčí, a Generic jen jednu – Adapter. Junior, který se rozhodne přidat SomeBusinessRule.php do src/Generic/Auth/, narazí na chybějící Domain/ adresář a dostane signál, že kód tam nepatří. Naopak Aggregate v src/Core/Pricing/Domain/ má kolem sebe celou doménovou infrastrukturu a tým u něj pracuje s invarianty do hloubky.

Tato struktura má ale cenu. Sekce 02.08 ukazuje, že klasifikace stárne a přehodnocuje se každých 12–18 měsíců. Jenže překlasifikace Pricing ze Core na Supporting v této struktuře znamená přejmenovat namespace v celém projektu – tedy přesně tu změnu, kterou tým odloží. Klasifikace zapsaná do cesty je pedagogicky nejsilnější a provozně nejkřehčí varianta.

Druhá varianta dává na první úroveň doménové jméno a klasifikaci nechává v dokumentaci:

text src/ (varianta podle subdomény)
1src/2├── Pricing/          ← Core (viz docs/domain/pricing.md)3│   ├── Domain/4│   ├── Application/5│   └── Infrastructure/6├── Ordering/         ← Supporting7├── Inventory/        ← Supporting8└── Auth/             ← Generic, jen Adapter/

Tu popisuje Matthias Noback v sérii Layers, ports & adapters (2017): přímo pod src/ adresář pro každý Bounded Context, uvnitř něj vrstvy. Při přeřazení Pricing mezi Supporting zůstává cesta src/Pricing/ stejná. Mění se jen hloubka modelu uvnitř, třeba ubude pár tříd, a use v celém projektu nikdo přepisovat nemusí. Platí se za to jinde: ze stromu adresářů už nikdo klasifikaci nevyčte, takže musí žít v Core Domain Chartu a v Domain Vision Statementech. Kniha dál používá právě tuto druhou variantu: od kapitoly o základních konceptech je všude App\Ordering\, App\UserManagement\ a podobně, tedy kontext na první úrovni. Rozdělení podle klasifikace ukazuje tato kapitola proto, že zviditelňuje její téma. Jako kostra projektu by se ale rozešlo se zbytkem knihy.

Autoload žádné zvláštní mapování nepotřebuje. Výchozí PSR-4 pravidlo Symfony skeletonu App\ → src/ pokryje App\Core\, App\Supporting\, App\Generic\ i App\SharedKernel\. Mapování jen na tyto čtyři prefixy bez App\ by naopak vyřadilo z autoloadu App\Kernel v src/Kernel.php:

json composer.json (platí pro obě varianty)
1{2    "name": "acme/eshop",3    "type": "project",4    "require": {5        "php": ">=8.4",6        "symfony/framework-bundle": "^8.0",7        "doctrine/orm": "^3.0",8        "symfony/messenger": "^8.0"9    },10    "autoload": {11        "psr-4": {12            "App\\": "src/"13        }14    },15    "autoload-dev": {16        "psr-4": {17            "App\\Tests\\": "tests/"18        }19    }20}

Namespace App\SharedKernel\ slouží pro skutečně sdílené primitivy: základní třídu AggregateRoot, DomainEvent, Money a obecné identifikátory. Používají je všechny subdomény a nepatří do žádné z nich. Kniha ho takto používá i v dalších kapitolách. Shared kernel má ovšem sklon rozrůstat se do anti-vzoru „všechno je sdílené“; rizika a pravidla rozebírá sekce o Shared Kernelu v kapitole o Context Mappingu.

V config/services.yaml pak obvykle stojí každá subdoména jako vlastní resource blok. DI definice tím zůstanou izolované na úrovni subdomény:

yaml config/services.yaml (výřez: struktura podle klasifikace)
1services:2    _defaults:3        autowire: true4        autoconfigure: true5 6    # Core subdomény: explicitní bind interfaces7    App\Core\Pricing\:8        resource: '../src/Core/Pricing/'9        exclude:10            - '../src/Core/Pricing/Domain/Event/'11            - '../src/Core/Pricing/Domain/ValueObject/'12 13    App\Core\Pricing\Domain\Repository\PricelistRepository:14        alias: App\Core\Pricing\Infrastructure\Doctrine\DoctrinePricelistRepository15 16    # Supporting: standardní autowire17    App\Supporting\:18        resource: '../src/Supporting/'19 20    # Generic: jen adaptéry, žádné doménové třídy21    App\Generic\:22        resource: '../src/Generic/'

Aggregate v Core subdoméně s hloubkou modelování, jakou si Core zaslouží:

php src/Core/Pricing/Domain/Aggregate/Pricelist.php
1<?php2 3declare(strict_types=1);4 5namespace App\Core\Pricing\Domain\Aggregate;6 7use App\Core\Pricing\Domain\Event\PricelistChanged;8use App\Core\Pricing\Domain\Event\PricelistCreated;9use App\Core\Pricing\Domain\Exception\ConflictingPriceRuleException;10use App\Core\Pricing\Domain\ValueObject\PriceRule;11use App\SharedKernel\Domain\AggregateRoot;12use App\SharedKernel\Domain\Money;13use Symfony\Component\Uid\Uuid;14 15final class Pricelist extends AggregateRoot16{17    /** @var list<PriceRule> */18    private array $rules = [];19 20    private function __construct(21        private readonly Uuid $id,22        private readonly string $name,23    ) {24    }25 26    public static function create(string $name): self27    {28        $pricelist = new self(Uuid::v7(), $name);29        $pricelist->record(new PricelistCreated($pricelist->id, $name));30 31        return $pricelist;32    }33 34    public function applyRule(PriceRule $rule): void35    {36        if ($this->hasConflictingRule($rule)) {37            throw ConflictingPriceRuleException::forCode($rule->code);38        }39 40        $this->rules[] = $rule;41        $this->record(new PricelistChanged($this->id, $rule));42    }43 44    public function priceFor(Money $listPrice, array $context): Money45    {46        $price = $listPrice;47        foreach ($this->rules as $rule) {48            if ($rule->matches($context)) {49                $price = $rule->apply($price);50            }51        }52        return $price;53    }54 55    private function hasConflictingRule(PriceRule $candidate): bool56    {57        foreach ($this->rules as $rule) {58            if ($rule->conflictsWith($candidate)) {59                return true;60            }61        }62        return false;63    }64}

Pro srovnání: ekvivalent v Supporting subdoméně (Order Management) je podstatně lehčí: Doctrine entita s pár metodami a bez separátních Value Objektů, protože invarianty jsou triviální. Jde vědomě o tentýž Order, který kapitola o návrhu agregátů modeluje jako plný Aggregate s Order::place(), CustomerId a Money. Žádná z obou ukázek není ta jediná správná; liší se klasifikací. Objednávky jsou v e-shopu s vlastním pricing enginem Supporting, v logistické firmě jsou Core – a model se tomu přizpůsobí. Ve stejném duchu je i holá \DomainException níže přiznanou zkratkou: pojmenované výjimky patří tam, kde na nich někdo staví reakci, ne do třístavového workflow.

php src/Supporting/Ordering/Domain/Order.php
1<?php2 3declare(strict_types=1);4 5namespace App\Supporting\Ordering\Domain;6 7use Doctrine\ORM\Mapping as ORM;8use Symfony\Component\Uid\Uuid;9 10#[ORM\Entity]11#[ORM\Table(name: "orders")]12class Order13{14    #[ORM\Id]15    #[ORM\Column(type: "uuid")]16    private Uuid $id;17 18    #[ORM\Column(length: 32)]19    private string $status = "pending";20 21    #[ORM\Column(type: "decimal", precision: 12, scale: 2)]22    private string $total;23 24    public function __construct(Uuid $id, string $total)25    {26        $this->id = $id;27        $this->total = $total;28    }29 30    public function confirm(): void31    {32        if ($this->status !== "pending") {33            throw new \DomainException("Order is not pending.");34        }35        $this->status = "confirmed";36    }37 38    public function cancel(): void39    {40        if ($this->status === "shipped") {41            throw new \DomainException("Cannot cancel shipped order.");42        }43        $this->status = "cancelled";44    }45 46    public function getId(): Uuid { return $this->id; }47    public function getStatus(): string { return $this->status; }48    public function getTotal(): string { return $this->total; }49}

Doctrine typ uuid není součástí ORM 3. Dodává ho Symfony Doctrine bridge a DoctrineBundle ho zaregistruje sám, jakmile je nainstalovaný symfony/uid. Bez tohoto balíčku skončí mapování #[ORM\Column(type: "uuid")] výjimkou o neznámém typu. Explicitní registrace níže tedy v projektu s DoctrineBundle nic nemění, jen typ zviditelní v konfiguraci.

yaml config/packages/doctrine.yaml (výřez: explicitní registrace typu)
1doctrine:2    dbal:3        types:4            uuid: Symfony\Bridge\Doctrine\Types\UuidType

A v Generic subdoméně (Auth0 integrace) není entita ani Aggregate. Při troše štěstí není ani vlastní adaptér: balíček auth0/symfony dodává hotový Auth0\Symfony\Security\UserProvider a authenticator, takže se celá subdoména smrskne na konfiguraci.

yaml config/packages/security.yaml (výřez: oddělený firewall)
1security:2    providers:3        auth0_provider:4            id: Auth0\Symfony\Security\UserProvider5 6    firewalls:7        main:8            provider: auth0_provider9            custom_authenticators:10                - auth0.authenticator

Vlastní UserProvider píšete až tehdy, když hotový nestačí. Typicky proto, že profil z Auth0 potřebujete obohatit o interní data. I pak zůstane u tenkého adaptéru, který implementuje rozhraní ze Symfony Security a nic nemodeluje:

php src/Generic/Auth/Adapter/Auth0UserProvider.php
1<?php2 3declare(strict_types=1);4 5namespace App\Generic\Auth\Adapter;6 7use Auth0\SDK\Auth0;8use Symfony\Component\Security\Core\Exception\UserNotFoundException;9use Symfony\Component\Security\Core\User\UserInterface;10use Symfony\Component\Security\Core\User\UserProviderInterface;11 12final class Auth0UserProvider implements UserProviderInterface13{14    public function __construct(private readonly Auth0 $auth0)15    {16    }17 18    public function loadUserByIdentifier(string $identifier): UserInterface19    {20        $response = $this->auth0->management()->users()->get($identifier);21        if ($response->getStatusCode() === 404) {22            throw new UserNotFoundException();23        }24        $profile = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);25 26        return new Auth0User($profile);27    }28 29    public function refreshUser(UserInterface $user): UserInterface30    {31        return $this->loadUserByIdentifier($user->getUserIdentifier());32    }33 34    public function supportsClass(string $class): bool35    {36        return $class === Auth0User::class;37    }38}

Třikrát jde o tentýž typ úlohy, práci s doménovým objektem, a pokaždé s radikálně jiným objemem kódu. Tak se rozhodnutí o investici propisuje do kódu.

02.07 Subdomény a sourcing strategie (build / buy / partner)#

Klasifikace má cenu, jen když z ní plynou rozhodnutí. Na sourcing strategii (kdo a jak kód napíše) se mapuje přímo:

Klasifikace Doporučená strategie Tým Příklady
Core BUILD in-house (vlastní IP, plná kontrola) Senior + doménový expert Pricing engine, Recommendation, Risk scoring
Supporting BUILD lehce nebo BUY hotové řešení, pokud existuje s ≥80% pokrytím Medior, junior pod dohledem Order mgmt (build), Helpdesk (buy – Zendesk)
Generic BUY / RENT / OPEN-SOURCE Junior, integrátor Auth (Auth0/Keycloak), Email (SES), Payments (Stripe)

Klasifikace je relativní k obchodnímu modelu, ne absolutní. Co je pro vás Generic, je pro vendora jeho Core, jak ukázaly příklady Mailgunu a autentizace výše.

U Generic subdomény má „koupit“ čtyři podoby, které Evans rozlišil už v roce 2003 a které se v praxi pletou do jedné [1]:

  1. Hotové řešení. Nejrychlejší cesta, ale platí se integrací a tím, že cizí model vnutí slovník. Evansův verdikt zní opatrně: obvykle se to nevyplatí, prozkoumat to ale stojí za to.
  2. Publikovaný design nebo model. Cizí je návrh, kód je váš. Evans odkazuje na Fowlerovy Analysis Patterns; dnešní ekvivalent jsou standardy typu OpenID Connect nebo ISO 20022.
  3. Outsourcovaná implementace. Zadání ven, integrace a údržba doma. Šetří kapacitu seniorního týmu, přidává komunikační režii a náklad na code review.
  4. Vlastní implementace. Dostanete přesně to, co chcete, nic navíc, a integrace odpadá. Cenou je údržba navždy.

Sourcing podle strategické hodnoty ostatně není objev DDD. Niel Nickolaisen popsal v Stand Back and Deliver (2009) Purpose Alignment Model se dvěma osami, mission critical a market differentiating [8]. Jeho čtyři kvadranty se na tabulku výše mapují skoro doslova. Differentiating (excelovat) odpovídá Core, Parity (zjednodušit a standardizovat) pokrývá Supporting i Generic. Partner je varianta popsaná níže a Who cares je práce, kterou má tým dělat co nejlevněji, nebo vůbec.

Pro rozhodování o nákupu z toho plyne jedna otázka před podpisem SaaS smlouvy: „kupujeme Generic, nebo outsourcujeme Core?“ Pokud SaaS pokryje Generic, je to čistý zisk: ušetříme čas, koupíme zkušenosti vendora, soustředíme se na Core. Pokud by SaaS pokryl Core, je to strategický ústup: odevzdáváme konkurenční výhodu třetí straně. Stejné rozhodnutí, ale opačné znaménko.

Vedle BUILD a BUY existuje třetí varianta: partnerství. Hodí se pro Supporting subdomény, kde hotové řešení existuje, ale potřebujete větší míru přizpůsobení, než dovolí standardní SaaS. Příklad: e-shop integruje fakturaci přes API jiné fintech firmy, která za měsíční poplatek počítá daně pro 30 jurisdikcí. Od nákupu se partnerství liší tím, že nejde o krabicový produkt, od vlastního vývoje tím, že kód píše cizí tým. Nese riziko dlouhodobé závislosti a vyžaduje smluvní jistoty (vlastnictví dat, exit clause, SLA) a Anti-Corruption Layer na hranici.

02.08 Evoluce subdomén v čase#

Klasifikace subdomén není jednorázové cvičení. Trh i technologie se za pár let posunou natolik, že z Core může být Supporting. Khononov tomu věnuje samostatnou sekci v Learning DDD: popisuje, jak k posunu typu subdomény dochází, jak ho poznat a jak na něj reagovat [3]. Formulace, že opomenutá re-evaluace stojí stejně jako špatná první klasifikace, je už závěr této knihy.

Posuny mají svá jména. Nick Tune je sepsal jako Core Domain Patterns a jeho slovník poslouží lépe než tři statické kategorie [6]. Bývalou inovaci, která dnes neodlišuje, ale pořád ji potřebujete, označuje jako Table Stakes / Former Core. Jádro, ze kterého se stala schopnost dostupná komukoliv, nazývá Commoditised Core. Opačný pohyb dostal jméno Black Swan Core: stane se něco nečekaného a zdánlivá komodita se přes noc promění v jádro. Hidden Core je jádro, které tým přehlédl právě proto, že vypadá nenápadně: vysoké odlišení při nízké složitosti.

Mechanismus za těmito posuny popsala Susanne Kaiser napojením klasifikace na Wardleyho evoluční osu genesis → custom-built → product → commodity [9]. Core začíná v genesis nebo custom-built, Supporting bývá custom-built či product, Generic sedí v product až commodity. Pohyb po ose jde jedním směrem a rovnou předepisuje metodu: co je v genesis, se staví doma; co dorazilo do product, se kupuje; co je commodity, se pronajímá. Posuny níže jsou různá místa na téže ose.

Z Generic do Core – komodita se stane diferenciátorem

Příklad: online platby v roce 2010 byly pro většinu firem Generic. Koupíte si bránu, integrujete, hotovo. Pro Stripe, který tehdy začínal, to byl ale Core: investoval do API, do podpory pro vývojáře, do globálního pokrytí. S jednosměrnou Wardleyho osou to není v rozporu: Stripe platby nekupoval jako komoditu, ale stavěl z nich nový produkt. Dnes je Stripe víceméně oborový standard a jádro jeho byznysu zůstává u plateb, jen se posunula laťka (fraud detection, tax compliance, finanční produkty pro startupy). Když firma zjistí, že se v dosud generické oblasti dá hrát o trh, je namístě posunout ji do Core a zvýšit investici. Riziko: pokud se mýlíte, utratíte peníze v subdoméně, kterou trh vůbec neoceňuje.

Z Core do Supporting – komoditizace

Příklad: synchronizace souborů. Dropbox měl v roce 2008 Core právě v ní. Šlo o nepříjemný problém s race conditions, latencemi a propagací binárních diffů, který tehdy nikdo neřešil dobře. Od nástupu Google Drive, OneDrive a iCloud Drive (2012–2014) je synchronizace komodita, kterou platformy přibalují k operačnímu systému nebo kancelářskému balíku. Dropbox proto musel posunout Core jinam, do produktivních nástrojů (Paper, integrace), aby zůstal odlišený. Jakmile je Core Domain dostupná jako služba u tří velkých vendorů, je čas snížit investici, refaktorovat model na lehčí a hledat nový diferenciátor.

Ze Supporting do Generic – když dorazí kvalitní SaaS

Příklad: helpdesk / ticketing. V roce 2005 si řada středních firem psala vlastní helpdesk modul jako Supporting subdoménu. Dnes Zendesk, Freshdesk nebo Intercom pokryjí 90 % požadavků a vlastní implementace se nevyplatí. Samotný ticketing se tak posunul ze Supporting do Generic a tým, který ho dál udržuje sám, plýtvá rozpočtem. Supporting zůstávají jen firemní pravidla podpory, která nad ním běží (eskalace, vazba na objednávky). Proto má Customer Support v tabulce v sekci 02.05 klasifikaci Supporting a hotový SaaS za ACL.

Kam hranici Generic posunuly jazykové modely

Poslední posun je čerstvý a zasáhl celou třídu úloh naráz. Klasifikace textu, extrakce dat z nestrukturovaných dokumentů, sumarizace, jazyková normalizace vstupů: ještě v roce 2020 to byly Supporting subdomény, na kterých seděl vlastní tým s vlastním modelem. Dnes jde o volání API. Ekonomika rozhodnutí se otočila. Kde dřív vedla jediná cesta přes vlastní implementaci, existuje teď hotové řešení. Jeho kvalita roste rychleji, než by tým stihl napsat vlastní.

Tvrdá čísla k tomuto posunu žádný primární DDD zdroj zatím nenabízí, takže jde o pozorování, ne o doložený trend. Praktický dopad je ale jednoznačný: subdomény, kolem kterých se točí zpracování textu, patří do nejbližšího auditu jako první. Souvislosti rozvádí kapitola o DDD a umělé inteligenci.

Praktická obrana proti zastarávání klasifikace:

  1. Naplánujte strategický audit subdomén každých 12–18 měsíců. Workshop na půl dne s product managementem a architekty.
  2. Po každém auditním cyklu projděte pětibodový test (sekce 02.03) na všech subdoménách znovu – i na těch, kterými „jste si jistí“.
  3. Srovnávejte s konkurencí: pokud váš největší konkurent neutralizuje vaši Core subdoménu (např. tím, že koupil hotové řešení, které je 80 % vašeho rozsahu), je to varovný signál.
  4. Sledujte nabídku SaaS v Generic subdoménách – co bylo loni „kupte si“, může být letos „má to každý a stojí to dvacetinu“.

02.09 Praktický postup – krok za krokem#

Pětikrokový postup pro první klasifikaci subdomén vlastního produktu. Doporučená délka workshopu: půl dne, 5–8 účastníků (architekt, tech-lead, product manager, doménový expert, případně CTO / VP product).

  1. Vypsat všechny capability / use-case.

    Použijte obchodní slovník, ne IT žargon. Příklady: „objednat zboží“, „sledovat zásilku“, „získat doporučení produktu“, „přihlásit se“, „obdržet účtenku e-mailem“, „reklamovat“. Cíl: 20–40 položek u středně velkého produktu. Pokud máte víc, agregujte. Pokud méně, buďte ostražití, pravděpodobně vám něco uniklo.

    Brainstorm nad prázdnou tabulí je nejhorší způsob, jak k seznamu dojít. Komunitní praxe (DDD Starter Modelling Process) klasifikaci zařazuje až za Big Picture Event Storming: capability se odečtou z časové osy událostí, kterou tým právě nakreslil. Kde hranice zůstávají sporné, pomůže sada Independent Service Heuristics od autorů Team Topologies. Jsou to otázky typu „dala by se tato část provozovat jako samostatný SaaS produkt s vlastní cenovkou?“, kterým rozumí i netechnický účastník workshopu.

  2. U každé položky odpovědět na pětibodový test (sekce 02.03).

    Tabulka 6 sloupců: capability + 5 otázek. Každý účastník odpovídá nezávisle (anonymně, Post-it nebo Miro), pak shrnete medián. Pozor na syndrom hrdiny. Pokud se týmový advokát konkrétní oblasti účastní hlasování, přiřaďte jeho hlasu nižší váhu. Jinak hlasování ratifikuje stávající advokacii místo toho, aby ji testovalo.

  3. Seskupit do subdomén.

    Capability, které sdílejí obchodní slovník a klasifikaci, patří do jedné subdomény. Příklad: „nabídnout slevu“, „aplikovat kupon“, „spočítat finální cenu“ → subdoména Pricing. Orientační počet subdomén, opět pravidlo palce bez opory v primárním zdroji: 8–15 u středního produktu, 20–40 u velkého enterprise systému. Pokud jich máte 5, je to podezřele málo (Supporting subdomény se typicky schovávají uvnitř Core); pokud 60, je to moc (typicky neagregujete capability).

  4. Pro každou subdoménu rozhodnout sourcing.

    Použijte tabulku ze sekce 02.07. U Core potvrďte BUILD; u Generic porovnejte 2–3 SaaS varianty (cena, stabilita vendora, složitost ACL); u Supporting rozhodněte BUILD vs. BUY. Výstupem kroku je seznam vybraných vendorů a interních týmů, které dostávají rozpočet.

  5. Zapsat do Domain Vision Statement (1 stránka A4) – kdo, co, proč, kdy.

    Pro každou Core subdoménu vytvořte jednostránkový dokument (markdown nebo Notion / Confluence). Pro Supporting stačí jedna věta v interním wiki. Pro Generic stačí poznámka „kupujeme X od vendora Y, smlouva platí do Z“. Tento dokument je jediný výstup workshopu, který přežije. Bez něj se klasifikace ztratí.

Postup výše počítá s tím, že hranice teprve vznikají. V existujícím monolitu, kde je pricing rozprostřený přes čtyřicet tříd a tři kontrolery, krok 3 selže: capability se nedají seskupit podle kódu, protože kód žádné skupiny nemá. Klasifikujte proto podle obchodních schopností a teprve pak hledejte, kudy jimi vede řez v existujícím kódu. Core Domain Charts na to mají variantu Architecture Migration, která do grafu kreslí současný a cílový stav. Postup extrakce popisuje kapitola o migraci z CRUD.

Šablona Domain Vision Statementu

Domain Vision Statement (DVS) je krátký dokument, který pro Core subdoménu definuje co, proč, kdo, kdy. Vychází z Evansova vzoru v kapitole „Distillation“, v agilní podobě zkrácené na jednu stránku markdownu:

markdown docs/domain/pricing.md
1# Pricing – Core Domain2 3## What4Dynamický pricing s personalizovanými promo kódy.5Vstupy: zákaznický segment, historie nákupů, sklad, čas dne.6Výstup: cena pro konkrétního zákazníka v konkrétním kontextu.7 8## Why core9Konkurenti používají statický pricing nebo banální slevové kódy.10Náš dynamic pricing engine generuje +18 % marže oproti statickému (A/B test 2026 Q1).11Bez něj jsme jen další e-shop.12 13## Investment14Tým: 3 senior PHP devs + 1 data scientist + product owner na full-time.15Stack: vlastní engine v PHP 8.4 / Symfony 8, žádné SaaS v jádru.16Persistence: PostgreSQL (rule store) + Redis (cache).17ML model: Python sidecar service, gRPC API.18 19## Bounded contexts20- Catalog BC (read model – indikativní cena)21- Checkout BC (write model – finální cena, validace, invariants)22- Recommendation BC (cross-context, čte z Pricing eventy)23 24## Off-limits25Žádný outsourcing. Žádné low-code platformy.26Žádný junior bez code review od tech-leadu.27Žádné rozhodnutí o pricing logice bez VP product.28 29## KPI30- Marže (cíl +20 % oproti baseline)31- Latence ceny < 50 ms p9932- Konzistence cena katalog vs. checkout < 0,1 %33 34## Re-evaluace35Každých 12 měsíců – pokud konkurence dorovná, posuneme do Supporting.

DVS je živý dokument a mění se se strategií, vendorem, týmem nebo KPI. Když ho půl roku nikdo neaktualizoval a Core Domain pořád existuje, je něco špatně. Buď se v ní nic neděje, a pak možná není Core, nebo dokument nikdo neudržuje, a pak ho ani nikdo nečte.

02.10 Shrnutí#

Bez subdoménové klasifikace nemá smysl řešit Bounded Contexts, Aggregaty ani Doctrine mapping. Modelujete naslepo a rozpočet se rozplývá rovnoměrně po nedůležitých částech aplikace.

Hlavní pravidla na zapamatování:

  1. Core Domain je vzácné, ne jediné – žádné číselné pravidlo neexistuje, firma soutěžící na několika osách může mít Core subdomén víc. Platí ale, že Core je z podstaty složité a mění se; jednoduchá subdoména dá jen krátkodobou výhodu. Sem směřuje většina modelovacího úsilí, senior tým a vlastní IP.
  2. Supporting subdomén je většina – řádově 50–60 % vývojové kapacity, stejná jednotka jako v sekci 02.04 (pravidlo palce, ne měřená data). Lehký DDD nebo Doctrine ORM CRUD, mediorní tým, ochota sáhnout po hotových řešeních, kde to jde. Cíl: fungovat spolehlivě s minimální údržbou. Vysoká složitost ve Supporting je signál k re-klasifikaci.
  3. Generic se výchozím rozhodnutím kupuje – autentizace, e-maily, platby, fulltext. Vlastní implementaci obhájíte jen tehdy, když integrační náklad převýší ten implementační. Tenký Anti-Corruption Layer chrání naše modely před cizím slovníkem.
  4. Mapování subdomén na Bounded Contexty není automaticky 1:1 – ideál je 1:1, časté je N:1 u drobných Supporting a Generic. Rozdělit souvislou funkcionalitu do víc kontextů se vyplatí jen z provozních důvodů (oddělené vývojové cykly, nezávislé škálování). Subdoména je obchodní hranice, BC implementační; zaměnitelné nejsou.
  5. Klasifikace stárne – přehodnocuje se každých 12–18 měsíců. Generic se může stát Core (Stripe), Core se může stát Supporting (synchronizace souborů), Supporting se může stát Generic (helpdesk). Tým, který nemá aktuální klasifikaci, neumí prioritizovat.

Subdoménová klasifikace slouží k rozhodování o investici, ne k estetickému dělení kódu. Kapitola splnila účel, pokud z ní vznikl seznam subdomén vlastního produktu a u každé rozhodnutí o sourcing strategii. Zůstal-li jen dojem „takto by se to dalo kategorizovat“, projděte ji znovu s konkrétním projektem v ruce.

Časté otázky

Jaký je rozdíl mezi subdoménou a Bounded Contextem?

Subdoména je obchodní hranice, tedy kus problému, který se v organizaci řeší jako jedna kapitola. Existovala obvykle dříve, než vznikl IT systém („prodej“, „logistika“, „personalistika“). Bounded Context je implementační hranice: místo, kde platí jeden Ubiquitous Language a jeden konzistentní model, typicky i jeden tým. Samostatná služba, nebo modul monolitu? To už je provozní rozhodnutí. Vztah nemusí být 1:1: jedna subdoména může být rozdělena do více BC, nebo více subdomén může žít v jednom BC (typické pro drobné Supporting a Generic). Detail v sekci 02.05 Mapování subdomén na BC.

Můžu změnit klasifikaci subdomény v průběhu života produktu?

Ano. Klasifikace stárne a re-evaluace každých 12–18 měsíců je nutnou součástí strategického DDD. Typické posuny: Generic se stává Core (online platby pro Stripe v roce 2010), Core se stává Supporting (synchronizace souborů pro Dropbox po nástupu Google Drive a OneDrive), Supporting se stává Generic (helpdesk po nástupu Zendesku). Re-klasifikace má praktický důsledek: jiná investice, jiný tým, jiná sourcing strategie. Detail v sekci 02.08 Evoluce subdomén v čase.

Jak poznám, že je subdoména Generic?

Generic subdoména je komoditizovaná: řešení existuje roky, prodává se jako SaaS, knihovna nebo open-source a tržní standard určuje, jak má vypadat. Typické příklady vedle autentizace: generování PDF faktur (hotové knihovny a fakturační služby) a rozesílání transakčních e-mailů (SMTP je standardizovaný protokol, doručitelnost řeší vendor). Od Supporting ji odlišuje složitost: Generic je složitý, ale už vyřešený problém, který se nemění, zatímco Supporting bývá jednoduchý. Výchozím rozhodnutím je nákup plus tenký Anti-Corruption Layer na hranici; vlastní implementaci obhájíte jen tehdy, když integrace vyjde dráž než napsání. Detail v sekci 02.02 Tři osy, ne jedna.

Kolik subdomén je „normální“ počet?

Orientačně u středního produktu 8–15 subdomén, u velkého enterprise systému 20–40. Jde o pravidla palce této knihy, ne o měřená data. Pokud máte méně než 8, typicky se Supporting subdomény schovávají uvnitř Core; pokud máte víc než 40, neagregujete capability dostatečně a pracujete v příliš jemné granularitě. Rozdělení vývojové kapacity vychází přibližně na 20–30 % Core, 50–60 % Supporting a 10–20 % Generic. Pět a víc Core subdomén není samo o sobě chyba, protože firma soutěžící na několika osách jich může mít víc. Zaslouží si ale kontrolní otázku: čím konkrétně se každá z nich odlišuje na trhu?

Co když vyjde, že nemáme žádnou Core Domain?

Je to legitimní výsledek a často signál, že plné DDD nestojí za náklady. Pokud po pětibodovém testu (sekce 02.03) nezůstane ani jedna subdoména s 3+ ANO, váš produkt je zřejmě „lepší CRUD“: kombinace komoditizovaných řešení (Generic) a interní administrativy (Supporting) bez skutečného diferenciátoru. V takovém případě se nabízí CRUD architektura se servisní vrstvou (Transaction Script) nad Doctrine entitami; investice do plného taktického DDD by se nevrátila. Detailní rozbor v kapitole Kdy DDD nepoužívat.

Musí každá Core subdoména mít vlastní Bounded Context?

Cílem je 1:1. Rozdělení souvislé funkcionality do víc kontextů je spíš varovný signál: tatáž změna požadavků pak zasáhne oba kontexty a vynutí si současné nasazení. Oprávněné důvody k rozdělení jsou provozní: potřeba oddělit vývojové cykly komponent nebo škálovat část nezávisle na zbytku. Příklad z e-shopu: Pricing (Core) žije v Catalog BC i v Checkout BC, protože listing a checkout mají odlišné výkonnostní nároky a nasazují se různým tempem. Bez takového důvodu se subdoména nedělí.

02.11 Další četba#

Zdroje ke strategickému DDD a klasifikaci subdomén:

  • Domain Language – oficiální stránky Erica Evanse, kde najdete DDD Reference (zdarma) shrnující strategické vzory včetně Core Domain a Generic Subdomains.
  • Implementing Domain-Driven Design – Vaughn Vernon, kapitola 2 „Domains, Subdomains, and Bounded Contexts“ je referenční čtení pro tuto kapitolu.
  • Learning Domain-Driven Design – Vlad Khononov (O'Reilly 2021), kapitola 1 „Analyzing Business Domains“ je nejaktuálnější výklad subdoménové klasifikace.
  • EventStorming – Alberto Brandolini, workshopová technika pro identifikaci subdomén ve velkém měřítku (Big Picture Event Storming).
  • Martin Fowler – Bounded Context – krátká, ale výstižná definice BC, která pomáhá odlišit ho od subdomény.
  • Core Domain Charts (DDD Crew) – open-source šablony pro vizualizaci subdoménové klasifikace, včetně katalogu otázek k oběma osám a varianty Architecture Migration.
  • Core Domain Patterns – Nick Tune, slovník osmi vzorů (Decisive Core, Commoditised Core, Black Swan Core, Suspect Supporting a další) pro popis posunů v čase.
  • Revisiting the Basics of DDD – Vlad Khononov, kratší předloha první kapitoly Learning DDD: tři osy klasifikace a otázka, zda by se subdoména dala prodat jako samostatný byznys.
  • Patterns, Principles and Practices of DDD – Scott Millett a Nick Tune, kapitola 3 „Focusing on the Core Domain“ a heuristika „code for replacement rather than reuse“ pro Supporting subdomény.
  • Architecture for Flow – Susanne Kaiser propojuje klasifikaci subdomén s Wardleyho mapami a Team Topologies.
  • Independent Service Heuristics – sada otázek pro hledání hranic subdomén, srozumitelná i netechnickým účastníkům workshopu.