Subdomény: Core, Supporting, Generic
Než vytvoříte první Aggregate, rozhodněte, kde to vůbec dává smysl. Subdomény jsou Evansův strategický filtr: tři kategorie, které určují, kolik úsilí, jakou seniority a jaký technologický stack si konkrétní část aplikace zaslouží.
Obsah kapitoly
02.01 Proč subdomény předcházejí všemu ostatnímu
Vývojářský reflex „naimplementuju to celé pořádně“ je drahý a v naprosté většině produktů marný. Ne každá část aplikace si zaslouží stejnou hloubku modelování – naopak, pokus modelovat všechno stejně pečlivě je jednou z nejspolehlivějších cest, jak vyčerpat rozpočet ještě dřív, než tým dojde k tomu, co zákazníka skutečně zajímá. Eric Evans v Domain-Driven Design (2003), kapitola 15 „Distillation”, proto zavádí strategický filtr: před prvním Aggregate, prvním Value Objectem, prvním UseCaseHandlerem si poctivě odpovězte, která část domény je vaše konkurenční výhoda, která je nutné zlo a kterou nedává smysl vůbec psát. Tomuto filtru se říká rozdělení domény na Core, Supporting a Generic subdomény [1].
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 s jedním deployment artefaktem. Subdoména je naproti tomu byznysová hranice – kus problému, který se v organizaci řeší jako jedna ucelená kapitola. Vztah mezi nimi není 1:1: jedna subdoména („Pricing“) může být rozdělena do více Bounded Contexts (BC „Catalog“ počítá indikativní cenu, BC „Checkout“ počítá závaznou cenu se slevami a daněmi), a naopak jeden BC může pokrývat více malých subdomén najednou (BC „Backoffice“ obvykle obsahuje kousky reportingu, fakturace i správy uživatelů).
Vaughn Vernon v Implementing Domain-Driven Design (2013), kapitola 2 „Domains, Subdomains, and Bounded Contexts“, to formuluje pragmaticky: doména je celý problémový prostor organizace; subdoména je jeho logická část; Bounded Context je řešení, které pro tu část navrhujete [2]. Vlad Khononov v Learning Domain-Driven Design (O'Reilly 2021), kapitola 1 „Analyzing Business Domains“, k tomu doplňuje, že klasifikace subdomén je první nástroj, který v DDD máte k dispozici, a zároveň ten nejlevnější – stojí jediný workshop a změní distribuci milionů korun rozpočtu [3].
V souladu s tím, co o strategickém DDD říká i úvodní kapitola, platí, že chyba ve volbě subdomény vás bude stát násobně víc než chyba v jednotlivém Aggregate. Špatně navržený Aggregate refaktorujete za dva sprinty; špatně klasifikovaná Core Domain znamená, že jste rok rozvíjeli něco, co jste měli koupit, a zároveň jste odsunuli to, čím jste se měli odlišit od konkurence. Proto si tato kapitola dává za cíl naučit vás filtrovat dřív, než modelujete.
02.02 Tři kategorie subdomén
Evansovo rozdělení je záměrně hrubé – tři škatulky, žádný odstín. Důvod je praktický: jakmile se v každé z nich rozhodnete pro investici, podrobnost už řeší taktické úrovně (Aggregate, Repository, Domain Service). Strategická úroveň potřebuje jen 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 – 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.“ Nebo formulováno opačně: pokud byste si stejnou funkcionalitu mohli stejně levně koupit od dodavatele, není to Core, je to Generic.
Důsledky pro tým a stack: plný taktický DDD 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íce automatizovaných testů, nejvíce code review a nejvíce diskusí s doménovými experty. Khononov uvádí jako tržní příklad algoritmus dynamického ratingu Uberu, fulfillment optimalizaci Amazonu nebo doporučovací systém Spotify – v každé z těch firem je „komodita pro ostatní“ v jejich vlastním pohledu Core Domain a investují do toho odpovídající rozpočet [3].
- 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 – ale kdyby vůbec nefungoval, provoz by stál.
Důsledky pro tým a stack: lehký DDD (často stačí anemic model s těžkým Doctrine ORM), juniorní až mediorní tým, ochota použít hotová řešení, kde dávají smysl. Cílem je fungovat spolehlivě s minimálními náklady na údržbu, ne mít nejhezčí model. Vernon doporučuje pro Supporting subdomény skutečně používat lehčí variantu DDD – pokud by se na nich uplatnil plný taktický design, organizace zbytečně utratí seniorní kapacitu na něco, co nikoho nezajímá [2].
- 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ů, platební bránová integrace, generování PDF faktur, fulltext, antispam. V Generic subdoméně je custom kód anti-vzor – důkaz, že tým v té oblasti znovuobjevuje kolo a ubírá z rozpočtu Core Domény.
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 na Auth0 / Keycloak, Stripe, Mailgun, AWS SES, Algolia. Velikostní pravidlo: pokud na konkrétní Generic subdoméně sedíte víc než 5–10 % vývojové kapacity, něco je špatně – buď jste si vybrali špatný produkt, nebo jste si nesprávně klasifikovali subdoménu.
02.03 Jak rozpoznat Core Domain – pětibodový test
Nejtěžším krokem je rozpoznat právě tu jednu Core Domain. Týmy mají sklon o všem prohlašovat, že je to „strategicky důležité“, což pojem Core Domain devalvuje na bezvýznamný štítek. Následující pětibodový test vznikl jako kombinace heuristik z Khononova [3] a Brandoliniho diskuse o Core Domain Charts [4]. Každou položku ohodnoťte ANO/NE. Pokud máte tři a více ANO, kandidát na Core Domain. Pokud máte tři a více NE, je to Supporting nebo Generic.
-
„Pokud bychom to outsourcovali, můžeme i tak prodávat hlavní produkt?“
Pokud ANO → není to Core. Pokud NE (= bez té funkcionality nemáme co prodávat) → kandidát na Core. Příklad: e-shop může outsourcovat platby (NE Core), ale nemůže outsourcovat svůj sortiment a způsob jeho doporučování (kandidát na Core).
-
„Existuje tržní benchmark / standard?“
Pokud ANO → 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.1 / OpenID Connect pro autentizaci, ISO 8583 pro karetní platby, RFC 5321 pro SMTP. Pokud ANO ↔ Generic, pokud NE ↔ neutrální (může být Core i Supporting).
-
„Píšeme to už podruhé jinak než konkurence?“
Pokud ANO → silný indikátor Core. Vývoj „jinak než ostatní“ je nákladný a smysl má jen tehdy, pokud z té odlišnosti plyne tržní výhoda. Pokud děláme něco jinak bez hmatatelné výhody, je to často špatně klasifikovaná subdoména – měli jsme koupit standardní řešení.
-
„Mluví o tom CEO / VP product na týdenní bázi?“
Pokud ANO → silný indikátor Core. Vrcholný management se nezabývá Supporting subdoménami; o těch slyší jen tehdy, když přestanou fungovat. Pokud o nějaké funkcionalitě průběžně rozhoduje CEO, je to konkurenční diferenciátor – tedy Core. Pokud ne, je to provoz.
-
„Plánujeme v této oblasti experimentovat / měnit pravidla často?“
Pokud ANO → Core. Frekvence změn je proxy pro to, 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 Doméně 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 je třeba vyvolat ještě před začátkem implementace.
02.04 Anti-vzor: „všechno je Core“
Nejčastější chyba ve strategickém DDD se nejmenuje „špatně navržený Aggregate“, ale „všechno je Core“. Týmy mají k této chybě silný psychologický sklon: každý vývojář, kterého se zeptáte, jestli je jeho oblast strategická, řekne ANO – ego, kariérní obavy z toho, že na „nedůležitém“ se nedá vybudovat seniorní pozice, a obecná lidská tendence připisovat své práci větší význam, než má. Brandolini tomu říká „hero syndrome“: každá funkcionalita má svého hrdinu, který ji obhajuje jako nezbytnou pro firmu [4].
Manažerská rovina ten anti-vzor zhoršuje. Ředitel bez technického zázemí slyší od každého vedoucího týmu, že jeho oblast je strategická, a nemá nástroj, jak vyhodnotit, kde je investice opodstatněná a kde je to obhajoba pozic. Výsledek: rozpočet se rozteče po všech subdoménách rovnoměrně, Core Doména dostane stejně peněz jako fakturace, a do dvou let je firma předběhnuta menším konkurentem, který soustředil pětinásobnou investici do svého reálného Core.
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. Refactor 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řekvapivě jednoduchá: vynuťte si rozpočet. Před začátkem každého kvartálu (nebo OKR cyklu, podle toho, jak váš tým plánuje) si nakreslete tři škatulky – Core / Supporting / Generic – a do každé napište procentní podíl celkové vývojové kapacity. Pokud vám do Core spadne 80 %, není to 80 % Core, ale 80 % iluze. Realistická distribuce u průměrné B2B SaaS firmy je 20–30 % Core, 50–60 % Supporting, 10–20 % Generic (poslední číslo má být malé, protože Generic se z definice nepíše, jen se integruje).
02.05 Mapování subdomén na Bounded Contexts
Subdoména a Bounded Context se mapují skrz tři standardní vztahy: 1:1 (jedna subdoména = jeden BC, ideální stav), 1:N (jedna subdoména je rozdělená do více BC, typické pro Core), 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 upozorňuje, že u Core Domén je 1:N často nevyhnutelné, protože stejné doménové pravidlo se uplatňuje v různých kontextech (čtenářském vs. zápisovém) [3].
Pro názornost mapujme imaginární e-shop střední velikosti (3–4 týmy, 25 vývojářů) na subdomény a Bounded Contexts:
| Subdoména | Klasifikace | Bounded Context(s) | Vztah | Poznámka |
|---|---|---|---|---|
| Pricing & Promotions | Core | Catalog BC, Checkout BC | 1:N | Sdílené pravidlo „cena“ se aplikuje na čtení (katalog) i zápis (checkout) odlišně. |
| 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. Pokud by ve vašem produktu vyšlo radikálně jiné rozložení (např. 5 Core BC + žádný Generic), je to signál pro re-validaci klasifikace.
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: ztratíte schopnost na první pohled poznat, do které subdomény funkcionalita patří, a junior, který hledá „jak se počítá cena“, musí proběhnout všechny tři adresáře. Doporučovaná alternativa zní: strukturovat src/ primárně podle subdomén, sekundárně podle vrstev uvnitř subdomény.
Konkrétní rozložení v Symfony 8 e-shopu:
src/
├── Core/
│ ├── Pricing/ ← plný DDD: Aggregate, VO, Domain Event
│ │ ├── Domain/
│ │ │ ├── Aggregate/Pricelist.php
│ │ │ ├── ValueObject/Money.php
│ │ │ ├── ValueObject/PriceRule.php
│ │ │ ├── Event/PricelistChanged.php
│ │ │ └── Repository/PricelistRepository.php (interface)
│ │ ├── Application/
│ │ │ ├── Command/UpdatePriceCommand.php
│ │ │ └── Handler/UpdatePriceHandler.php
│ │ └── Infrastructure/
│ │ ├── Doctrine/DoctrinePricelistRepository.php
│ │ └── Symfony/PricingMessageHandler.php
│ └── Recommendations/ ← analogická struktura
│
├── Supporting/
│ ├── Ordering/ ← lehký DDD: minimal Aggregate, Doctrine ORM
│ │ ├── Domain/
│ │ │ ├── Order.php (Doctrine entity + chování)
│ │ │ └── OrderRepository.php (interface)
│ │ ├── Application/
│ │ │ └── PlaceOrderHandler.php
│ │ └── Infrastructure/
│ │ └── Doctrine/DoctrineOrderRepository.php
│ ├── Inventory/
│ └── Reporting/
│
└── Generic/
├── Auth/ ← bridge na Auth0, žádný custom Aggregate
│ ├── Adapter/Auth0Client.php
│ ├── Adapter/Auth0UserProvider.php
│ └── Adapter/Auth0AuthenticationListener.php
├── Mail/
│ └── Adapter/SesMailerAdapter.php
└── Payment/
└── 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 tam ten kód nepatří. Naopak Aggregate v src/Core/Pricing/Domain/ má kolem sebe celou doménovou infrastrukturu a od týmu se očekává hluboká práce s invarianty.
Aby Symfony autoload fungoval, musí composer.json deklarovat odpovídající PSR-4 mapování:
{
"name": "acme/eshop",
"type": "project",
"require": {
"php": ">=8.4",
"symfony/framework-bundle": "^8.0",
"doctrine/orm": "^3.0",
"symfony/messenger": "^8.0"
},
"autoload": {
"psr-4": {
"App\\Core\\": "src/Core/",
"App\\Supporting\\": "src/Supporting/",
"App\\Generic\\": "src/Generic/",
"App\\Shared\\": "src/Shared/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}
Volitelný App\Shared\ namespace slouží na opravdu sdílené primitivy – Money, Uuid, DomainEvent base třída – které se používají napříč subdoménami a nepatří do žádné z nich. Pozor: Shared se rychle rozrůstá do anti-vzoru „shared kernel everywhere“; držte ho úzký a každou novou třídu v něm zdůvodňujte revieweru. Vernon i Khononov se shodují, že shared kernel je nejnebezpečnější vzor v DDD a má se používat výjimečně [2][3].
Symfony konfigurace pak obvykle vypadá tak, že config/services.yaml autowire-uje každou subdoménu jako resource blok, což izoluje DI definice na úrovni subdomény:
services:
_defaults:
autowire: true
autoconfigure: true
# Core subdomény: explicitní bind interfaces
App\Core\Pricing\:
resource: '../src/Core/Pricing/'
exclude:
- '../src/Core/Pricing/Domain/Event/'
- '../src/Core/Pricing/Domain/ValueObject/'
App\Core\Pricing\Domain\Repository\PricelistRepository:
alias: App\Core\Pricing\Infrastructure\Doctrine\DoctrinePricelistRepository
# Supporting: standardní autowire
App\Supporting\:
resource: '../src/Supporting/'
# Generic: jen adaptery, žádné doménové třídy
App\Generic\:
resource: '../src/Generic/'
Příklad konkrétního Aggregate v Core subdoméně, který demonstruje očekávanou hloubku modelování:
<?php
declare(strict_types=1);
namespace App\Core\Pricing\Domain\Aggregate;
use App\Core\Pricing\Domain\Event\PricelistChanged;
use App\Core\Pricing\Domain\ValueObject\Money;
use App\Core\Pricing\Domain\ValueObject\PriceRule;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Uuid;
final class Pricelist extends AggregateRoot
{
/** @var list<PriceRule> */
private array $rules = [];
public function __construct(
private readonly Uuid $id,
private readonly string $name,
) {
}
public function applyRule(PriceRule $rule): void
{
if ($this->hasConflictingRule($rule)) {
throw new \DomainException(
"Pravidlo $rule->code koliduje s existujícím pravidlem."
);
}
$this->rules[] = $rule;
$this->record(new PricelistChanged($this->id, $rule));
}
public function priceFor(Money $listPrice, array $context): Money
{
$price = $listPrice;
foreach ($this->rules as $rule) {
if ($rule->matches($context)) {
$price = $rule->apply($price);
}
}
return $price;
}
private function hasConflictingRule(PriceRule $candidate): bool
{
foreach ($this->rules as $rule) {
if ($rule->conflictsWith($candidate)) {
return true;
}
}
return false;
}
}
Pro srovnání: ekvivalent v Supporting subdoméně (Order Management) by byl podstatně lehčí – typicky Doctrine entita s pár metodami a bez separátních Value Objektů, protože invarianty jsou triviální:
<?php
declare(strict_types=1);
namespace App\Supporting\Ordering\Domain;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: "orders")]
class Order
{
#[ORM\Id]
#[ORM\Column(type: "uuid")]
private string $id;
#[ORM\Column(length: 32)]
private string $status = "pending";
#[ORM\Column(type: "decimal", precision: 12, scale: 2)]
private string $total;
public function __construct(string $id, string $total)
{
$this->id = $id;
$this->total = $total;
}
public function confirm(): void
{
if ($this->status !== "pending") {
throw new \DomainException("Order is not pending.");
}
$this->status = "confirmed";
}
public function cancel(): void
{
if ($this->status === "shipped") {
throw new \DomainException("Cannot cancel shipped order.");
}
$this->status = "cancelled";
}
public function getId(): string { return $this->id; }
public function getStatus(): string { return $this->status; }
public function getTotal(): string { return $this->total; }
}
A v Generic subdoméně (Auth0 integrace) není entita ani Aggregate – jen adaptér, který implementuje rozhraní z Symfony Security:
<?php
declare(strict_types=1);
namespace App\Generic\Auth\Adapter;
use Auth0\SDK\Auth0;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
final class Auth0UserProvider implements UserProviderInterface
{
public function __construct(private readonly Auth0 $auth0)
{
}
public function loadUserByIdentifier(string $identifier): UserInterface
{
$profile = $this->auth0->management()->users()->get($identifier);
if ($profile === null) {
throw new UserNotFoundException();
}
return new Auth0User($profile);
}
public function refreshUser(UserInterface $user): UserInterface
{
return $this->loadUserByIdentifier($user->getUserIdentifier());
}
public function supportsClass(string $class): bool
{
return $class === Auth0User::class;
}
}
Tři stejné akce („zacházení s entitou doménového objektu“), ale tři radikálně odlišné objemy kódu – to je strategická investice.
02.07 Subdomény a sourcing strategie (build / buy / partner)
Klasifikace subdomén nemá smysl, pokud z ní neplynou rozhodnutí. Přímé mapování klasifikace na sourcing strategii (kdo a jak ten kód napíše) je následující:
| 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) |
Často přehlížený detail: klasifikace závisí na perspektivě firmy. Pro běžnou B2C firmu je „CRM“ Generic – koupí Salesforce nebo HubSpot. Pro startup, který staví CRM jako svůj hlavní produkt, je „CRM“ Core. Stejně tak: „autentizace“ je Generic pro 99 % organizací, ale pro Auth0 / Okta je to jejich Core. Subdoménová klasifikace je vždy relativní k vašemu obchodnímu modelu, ne absolutní.
Praktický důsledek pro rozhodování o nákupu: než tým podepíše smlouvu na SaaS, položte si otázku – „kupujeme Generic, nebo si snižujeme 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.
Třetí varianta sourcingu – partnership – je vhodná pro Supporting subdomény, kde existuje hotové řešení, 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 provádí daňové výpočty pro 30 jurisdikcí. Není to BUY (žádná krabice), není to BUILD (cizí tým), je to partnerství s rizikem dlouhodobé závislosti – vyžaduje smluvní jistoty (data ownership, 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 se mění, technologie zrají, vaše konkurence se posouvá – a to, co bylo Core před třemi lety, může být dnes Supporting. Khononov tomu věnuje samostatnou sekci v Learning DDD a varuje, že nedokázat re-evaluovat klasifikaci je stejně drahá chyba jako klasifikovat ji špatně poprvé [3].
Tři typické posuny:
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: investovali do API, do podpory pro vývojáře, do globálního pokrytí. Dnes je Stripe víceméně oborový standard a u něj zůstává Core stále u plateb, jen s posunutou hladinou (fraud detection, tax compliance, finanční produkty pro startupy). Pokud vaše firma identifikuje, že se v určité dosud-Generic oblasti dá hrát o trh, posuňte ji do Core a zvyšte 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: cloud storage. Dropbox v roce 2008 měl Core v synchronizaci souborů – ošklivý problém s race conditions, latencemi a binární diff propagací, který nikdo jiný neuměl. Dnes je „cloud storage“ komoditizován cloud providerem (AWS S3, Azure Blob, GCS) a Dropbox musel posunout Core jinam – do produktivních nástrojů (Paper, integrace), aby zůstal odlišený. Pokud vidíte, že vaše Core Doména začíná být dostupná jako služba u tří velkých vendorů, nečekejte: snížíte investici, refaktorujete model na lehčí, hledáte nový diferenciátor.
Ze Supporting do Generic – když dorazí kvalitní SaaS
Příklad: helpdesk / ticketing. V roce 2005 většina středních firem implementovala vlastní helpdesk modul – Supporting subdoména. Dnes je Zendesk / Freshdesk / Intercom dost dobrý, aby pokryl 90 % požadavků, a vlastní implementace je nesmyslná. Subdoména se posunula z Supporting do Generic, a tým, který ji nadále udržuje vlastní, plýtvá rozpočtem.
Praktická obrana proti zastarávání klasifikace:
- Naplánujte strategický audit subdomén každých 12–18 měsíců. Workshop na půl dne s product managementem a architekty.
- Po každém audit cyklu projděte pětibodový test (sekce 02.03) na všech subdoménách znovu – i na těch, kde si „jste si jistí“.
- 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.
- Sledujte SaaS landscape 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
Konkrétní 5-krokový postup pro tým, který chce poprvé klasifikovat subdomény 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).
-
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; méně – buďte ostražití, pravděpodobně vám něco uniklo.
-
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 hero syndrome – pokud se týmový advokát konkrétní oblasti účastní hlasování, váhujte jeho hlas níž; jinak hlasování ratifikuje stávající advokacii místo toho, aby ji testovalo.
-
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. Cílový počet subdomén: 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 (typicky se schovávají Supporting do Core); pokud 60, je to moc (typicky neagregujete capability).
-
Pro každou subdoménu rozhodnout sourcing.
Použijte tabulku z sekce 02.07. U Core potvrďte BUILD; u Generic porovnejte 2–3 SaaS varianty (cena, vendor stability, ACL složitost); u Supporting rozhodněte BUILD vs. BUY. Výstupem kroku je seznam vendor zkratek a interních týmů, které dostávají rozpočet.
-
Zapsat do Domain Vision Statement (1 stránka A4) – kdo, co, proč, kdy.
Pro každou Core subdoménu vytvořte 1-strá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ý legitimní výstup workshopu – bez něj se klasifikace ztratí.
Šablona Domain Vision Statementu
Domain Vision Statement (DVS) je krátký dokument, který pro Core subdoménu definuje co, proč, kdo, kdy. Inspirovaný Evansovou kapitolou 14, ale zkrácený do moderního agile formátu – 15–20 řádků markdown:
# Pricing – Core Domain
## What
Dynamický pricing s personalizovanými promo kódy.
Vstupy: zákaznický segment, historie nákupů, sklad, čas dne.
Výstup: cena pro konkrétního zákazníka v konkrétním kontextu.
## Why core
Konkurenti používají statický pricing nebo banální slevové kódy.
Náš dynamic pricing engine generuje +18 % marže oproti statickému (A/B test 2026 Q1).
Bez něj jsme jen další e-shop.
## Investment
Tým: 3 senior PHP devs + 1 data scientist + product owner na full-time.
Stack: vlastní engine v PHP 8.4 / Symfony 8, žádné SaaS v jádru.
Persistence: PostgreSQL (rule store) + Redis (cache).
ML model: Python sidecar service, gRPC API.
## Bounded contexts
- Catalog BC (read model – indikativní cena)
- Checkout BC (write model – finální cena, validace, invariants)
- Recommendation BC (cross-context, čte z Pricing eventy)
## Off-limits
Žádný outsourcing. Žádné low-code platformy.
Žádný junior bez code review od tech-leadu.
Žádné rozhodnutí o pricing logice bez VP product.
## KPI
- Marže (cíl +20 % oproti baseline)
- Latence ceny < 50 ms p99
- Konzistence cena katalog vs. checkout < 0.1 %
## Re-evaluace
Každých 12 měsíců – pokud konkurence dorovná, posuneme do Supporting.
DVS by měl být živý dokument: aktualizujte ho, kdykoliv se mění strategie, vendor, tým nebo KPI. Pokud DVS nebyl aktualizován 6 měsíců a Core Doména pořád existuje, něco je špatně – buď se nic neděje (a pak možná není Core), nebo se nikdo neobtěžoval dokument udržovat (a pak ho nikdo nečte).
02.10 Shrnutí
Subdoménová klasifikace je první a nejdůležitější rozhodnutí strategického DDD. Bez ní 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í:
- Core Domain je vzácné – typická organizace má jednu, výjimečně dvě. Pokud jich vidíte víc, nemáte pět Core domén, máte iluze. Sem směřuje organizace většinu modelovacího úsilí, sem patří senior tým a vlastní IP.
- Supporting subdomén je většina – řekněme 60 % objemu kódu. Lehký DDD nebo Doctrine ORM CRUD, mediorní tým, ochota použít hotová řešení, kde dávají smysl. Cíl: fungovat spolehlivě s minimální údržbou.
- Generic se kupuje, ne píše – autentizace, e-maily, platby, fulltext. Custom kód v Generic subdoméně je anti-vzor a ubírá z rozpočtu Core. Tenký Anti-Corruption Layer chrání naše modely před cizím slovníkem.
- Mapování subdomén na Bounded Contexts není 1:1 – Core typicky 1:N (sdílené pravidlo v různých BC), Supporting 1:1, Generic přebírá cizí BC přes ACL. Subdoména je obchodní hranice, BC je implementační hranice; nezaměňujte je.
- Klasifikace stárne – re-evaluujte každých 12–18 měsíců. Generic se může stát Core (Stripe), Core se může stát Supporting (cloud storage), Supporting se může stát Generic (helpdesk). Tým, který nemá aktuální klasifikaci, neumí prioritizovat.
Kapitola záměrně skončila pravidlem, ne čtenářským vyústěním: subdoménová klasifikace je nástroj pro rozhodování, ne pro estetiku. Pokud z kapitoly odcházíte s konkrétním seznamem subdomén vlastního produktu a u každé z nich s rozhodnutím o sourcing strategii, kapitola splnila svůj účel. Pokud máte jen dojem „takto by se to dalo kategorizovat“, projděte si ji ještě jednou s vlastním projektem v ruce.
Časté otázky
Jaký je rozdíl mezi subdoménou a Bounded Contextem?
Subdoména je obchodní hranice – 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 jeden tým a jeden deployment. Vztah není 1:1: jedna subdoména může být rozdělena do více BC (Core často 1:N), nebo více subdomén může žít v jednom BC (typické pro Supporting / 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 (cloud storage pro Dropbox po nástupu S3), Supporting se stává Generic (helpdesk po nástupu Zendesk). 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 se rozhodnout, jestli je Identity / autentizace Core nebo Generic?
U 99 % organizací je Identity Generic – kupte si Auth0, Keycloak, Okta nebo Microsoft Entra ID. Custom auth je drahý anti-vzor: za rok skončíte u GDPR, SSO, WebAuthn, MFA a SOC 2 a budete refaktorovat to, co jste mohli koupit. Jediné firmy, pro které je Identity Core: poskytovatelé identity (sám Auth0, Okta), platformy s vlastním podpisovým schématem (kryptoměnové burzy, hardwarové autentikátory) a státní registry. Pro všechny ostatní platí: kupte, postavte ACL a pokračujte. Detail v sekci 02.04 Anti-vzor „všechno je Core“.
Kolik subdomén je „normální“ počet?
U středního produktu očekávejte 8–15 subdomén, u velkého enterprise systému 20–40. Pokud máte méně než 8, typicky se Supporting subdomény skrývají uvnitř Core; pokud máte víc než 40, neagregujete capability dostatečně a pracujete v příliš jemné granularitě. Distribuce by měla být přibližně 1–2 Core, 60 % Supporting, 20 % Generic – pokud máte 5+ Core, jde téměř jistě o tzv. hero syndrome a je nutná re-klasifikace.
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ě zvažte CRUD architekturu se servisní vrstvou, anemic model a Doctrine ORM; 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?
Ne nutně – Core subdoména bývá rozdělena do více BC, ne sloučena. Příklad: Pricing (Core) v e-shopu žije souběžně v Catalog BC (read model – indikativní cena pro listing) a v Checkout BC (write model – finální cena, validace, invariants). Důvod: čtenářský a zápisový kontext mají odlišné výkonnostní a konzistenční nároky a sloučení by vedlo k modelu, který nesedí ani jednomu. Naopak Supporting / Generic subdomény mají s BC obvykle 1:1 vztah pro jednoduchost.
02.11 Další četba
Pro další studium strategického DDD a klasifikace subdomén jsou doporučené následující zdroje:
- Domain Language – oficiální stránky Erica Evanse, kde najdete DDD Reference (zdarma) shrnující strategické vzory včetně Core / Supporting / Generic.
- 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 nejmodernější výklad subdoménové klasifikace.
- EventStorming – Alberto Brandolini, workshopová technika pro identifikaci subdomén ve velkém měřítku (Big Picture EventStorming).
- 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.