Kapitola 03 · Doménový model

Entity, vzťahy, časová dimenzia

Doménový model je postavený na rozhodnutiach, ktoré riešia 80 % budúcich problémov skôr, než vzniknú: identita oddelená od role, Affiliation a Facility ako prvotriedne entity, každý vzťah s časovou platnosťou, eventy ako zdroj pravdy.

01 · Prehľad

Entitný diagram

Nasledujúci diagram ukazuje hlavné entity systému a ich vzťahy. Affiliation je uprostred zámerne — takmer všetko v systéme sa ňou preplieta. Nie je to technický "join table", ale prvotriedna biznis entita s vlastným životným cyklom.

Entitný diagram SportUp AFFILIATION person × org × activity sport × discipline valid_from → valid_to PERSON interná identita ↔ RFO ORGANIZATION zväzy · kluby · obce komerčné ↔ RPO ACTIVITY_TYPE športovec · tréner rozhodca · dobrovoľník SPORT / DISCIPLINE 90 športov odvetvia · kategórie QUALIFICATION licencie s expiráciou CONSENT osoba × účel × prijímateľ FACILITY športovisko vlastník · prevádzkovateľ cestovný ruch EVENT podujatie + participation + venue (facility) DOCUMENT prehliadky · prílohy
Obrázok 2 · Entity a ich vzťahy. Affiliation a Facility sú kľúčové prvotriedne entity. Plné čiary = povinné referencie, prerušované = voliteľné/nepriame vzťahy.
02 · Person

Person — identita osoby

Person je stabilná entita, ktorá opisuje človeka ako ľudskú bytosť, nie jeho športovú rolu. Žiadne športové polia v Person tabuľke nie sú — kluby, licencie, role, všetko patrí do iných entít.

Povinné jadro

PoleTypPopis
person_idUUIDPrimárny kľúč, nemenný, interný
given_nameStringKrstné meno
family_nameStringPriezvisko
date_of_birthDateDátum narodenia
genderEnummale / female / other / undisclosed
nationalityISO 3166Štátna príslušnosť
verification_statusEnumunverified / self_declared / verified_by_org / verified_by_eid / verified_by_rfo
date_of_deathDate · nullableDátum úmrtia (notifikácia z RFO)

Identifikátory

PoleTypPopis
rfo_identifierString · encryptedIFO z RFO, len pri verifikácii
national_id_hashHashHash rodného čísla pre deduplikáciu
national_id_encryptedEncrypted blobRodné číslo — dešifruje sa len pri komunikácii s RFO
foreign_id_typeEnum · nullablepassport / residence_permit — pre cudzincov
foreign_id_valueString · encryptedHodnota zahraničného identifikátora
foreign_id_countryISO 3166Krajina vydania

Prečo UUID a nie rodné číslo

Rodné číslo nie je vhodný primárny kľúč. Cudzinci ho nemajú. Môže sa zmeniť. A hlavne — je to citlivý osobný identifikátor, ktorý nemá cestovať v API odpovediach a logoch. UUID sú stabilné a bez sémantickej hodnoty.

03 · Affiliation

Affiliation — srdce systému

Affiliation je vzťah osoby k organizácii v určitej role (druhu športovej činnosti), v určitom športe a disciplíne, platný v určitom časovom intervale. Je to najdôležitejšia entita celého systému a modeluje sa event-sourcovane.

Schéma

PoleTypPopis
affiliation_idUUIDPrimárny kľúč
person_idUUID · FKKtorá osoba
organization_idUUID · FKKtorá organizácia
activity_codeString · FKZ číselníka druhov športovej činnosti
legal_basis_codeString · FKZ číselníka právnych titulov
sport_codeString · FKZ číselníka športov
discipline_codeString · FK · nullableZ číselníka disciplín
category_codeString · nullableVeková / výkonnostná kategória
valid_fromDateZačiatok platnosti
valid_toDate · nullableKoniec platnosti (null = otvorený)
statusEnumpending / active / suspended / terminated
registration_numberString · nullableInterné ID pridelené organizáciou
source_app_idUUIDCertifikovaná aplikácia, ktorá záznam vytvorila

Kľúčové vlastnosti

  • Temporálnosť je vstavaná. Aktuálnosť sa odvodzuje z valid_from ≤ now() ≤ COALESCE(valid_to, ∞) a status = active.
  • Každá zmena je event. Prestup, pozastavenie, ukončenie nie je UPDATE, ale samostatná udalosť.
  • Kompozitné constrainty. Osoba nemôže mať dve aktívne "tréner" afiliácie v tom istom klube a športe súčasne.
  • Prestup = ukončenie + nový začiatok. Zachováva históriu.
04 · Organization

Organization — hierarchia subjektov

Organizácie nie sú ploché. Typológia pokrýva celý ekosystém — od národných zväzov cez samosprávu, štátnu správu, školstvo až po komerčné subjekty. Kompletný zoznam je v kapitole Číselníky.

Schéma

PoleTypPopis
organization_idUUIDPrimárny kľúč
org_typeString · FKZ číselníka druhov organizácií
legal_nameStringÚplný právny názov
icoString · nullableIČO (ak má)
legal_form_codeString · FKPrávna forma (z RPO číselníka)
rpo_identifierString · nullableIdentifikátor v RPO
rpo_registeredBooleanČi je v RPO
parent_org_idUUID · nullableNadradená organizácia (zväz → regionálny zväz)
addressObjectSídlo
valid_fromDateVznik
valid_toDate · nullableZánik
statusEnumactive / suspended / dissolved

Jedna organizácia, viac športov

Polyvalentný klub môže byť akreditovaný vo viacerých športoch súčasne. Preto vzťah Organization × Sport × Discipline je samostatná entita ORG_SPORT_ACCREDITATION s vlastnou platnosťou a stavom uznania.

05 · Facility

Facility — športovisko

Športovisko je v SportUp prvotriedna entita — nie atribút podujatia alebo klubu. Dôvod: jedno športovisko slúži viacerým subjektom súčasne, má vlastnú prevádzkovú a technickú identitu, jeho dáta sú zároveň podkladom pre agendu cestovného ruchu.

Schéma

PoleTypPopis
facility_idUUIDPrimárny kľúč
label_skStringNázov športoviska
facility_typeString · FKDruh podľa číselníka
owner_organization_idUUID · FKVlastník
operator_organization_idUUID · FK · nullablePrevádzkovateľ ak je iný
sports_supportedArray<sport_code>Aké športy sú tu možné
disciplines_supportedArray<discipline_code>Konkrétne disciplíny
locationObjectAdresa, obec, kraj, GPS súradnice
capacityInteger · nullableKapacita pre divákov
surfaceEnumPrírodná tráva, umelá tráva, tartan, parkety, ľad...
indoor_outdoorEnumindoor / outdoor / both
featuresArrayŠatne, parkovanie, bufet, bezbariérový prístup...
licensed_for_competitionsArrayÚroveň akreditácie pre súťaže
access_modelEnumVerejný voľný / platený / členský / komerčný
tourism_tagsArrayPríznaky pre cestovný ruch
opening_hoursObjectOtváracie hodiny, sezónnosť
statusEnumactive / under_construction / temporarily_closed / demolished

Prečo samostatná entita

  • Jedno športovisko, viac subjektov. Obecný štadión využíva klub, škola, ochotnícke ligy, komerčné nájmy.
  • Vlastník ≠ prevádzkovateľ. Obec vlastní, klub prevádzkuje. Model to rozlišuje explicitne.
  • Podujatia sa odohrávajú tu. Event Participation referencuje facility_id.
  • Cestovný ruch. Register je verejne dotazovateľný cez otvorené API — aplikácie typu "lyžiarske strediská v Žilinskom kraji" alebo "najbližšia plaváreň" čerpajú dáta odtiaľto.
  • Dotácie a investície. Ministerstvo vie, kde sú športoviská, v akom stave, s akou kapacitou. Dotačná politika nestojí na ad-hoc prieskumoch.

Vzťahy k ostatným entitám

Facility ↔ Organization

M:N cez entitu FACILITY_USAGE — ktoré organizácie majú pravidelnú prevádzku, akú a v akom časovom okne.

Facility ↔ Event

Podujatie sa koná na jednom alebo viacerých športoviskách. Entita EVENT_VENUE s časovým rozvrhom využitia.

Facility ↔ Location

Povinná referencia na číselník obcí, okresov a krajov. GPS súradnice sú povinné — bez nich nie je možné využitie v mapových službách.

Facility ↔ Person

Priame vzťahy neexistujú. Osoba je spojená so športoviskom len cez Organization (prevádzkovateľa) alebo cez Event Participation.

06 · Activity

Activity — druh športovej činnosti

Druh športovej činnosti (napr. amatérsky športovec, tréner, rozhodca, usporiadateľ, dobrovoľník) je hierarchický číselník. Každá Affiliation má práve jeden activity_code — ten istý človek v klube môže mať dve afiliácie, ak je zároveň hráčom aj trénerom.

Konkrétne druhy činností, ich kódy a popisy nájdete v kapitole Číselníky → Druhy športovej činnosti.

07 · Qualification

Qualification — licencie v čase

Kvalifikácia je napríklad trénerská licencia UEFA Pro, rozhodcovská licencia, športový lekár atestácia. Kvalifikácia je viazaná na osobu, nie na afiliáciu. Tréner s licenciou UEFA B si ju nesie so sebou aj keď zmení klub. Oprávnenie trénovať konkrétny tím je však kombináciou: Person + aktívna Qualification + aktívna Affiliation ako tréner.

PoleTypPopis
qualification_idUUIDPrimárny kľúč
person_idUUID · FKDržiteľ
qualification_typeString · FKZ číselníka kvalifikácií
issuing_organization_idUUID · FKKto vydal
levelString · nullableStupeň (Pro, A, B, C...)
issued_atDateDátum vydania
valid_fromDateZačiatok platnosti
valid_toDateKoniec platnosti
statusEnumactive / expired / revoked / suspended
document_idUUID · FKScan dokladu
09 · Eventy

Katalóg eventov pre Affiliation

EventKedy sa produkujeStav po evente
AffiliationRegisteredNová registrácia osoby v organizáciipending
AffiliationActivatedOverenie prešloactive
AffiliationSuspendedPozastavenie (zranenie, disciplinárka, admin)suspended
AffiliationResumedNávrat z pozastaveniaactive
AffiliationActivityChangedZmena druhu činnosti v tej istej organizáciiactive
AffiliationTerminatedKoniec vzťahuterminated
AffiliationCorrectedOprava chybného záznamubez zmeny

Prestup — ako saga

Čo je saga

Saga je vzor z distribuovaných systémov — rozloží jednu biznis operáciu, ktorá sa dotýka viacerých nezávislých entít, na sekvenciu menších samostatných krokov prepojených spoločným identifikátorom correlation_id. V event-sourced systéme platí pravidlo, že jeden event mení práve jeden agregát. Prestup sa však dotýka dvoch afiliácií naraz (pôvodnej a novej), a preto nemôže byť jeden event. Saga je technická odpoveď, ktorá zachováva jeden logický proces pri troch samostatných udalostiach.

Prestup športovca z klubu A do klubu B sa modeluje ako saga s tromi eventmi prepojenými cez correlation_id:

Prestup · sekvencia eventov
// 1) TransferRequested na novú (ešte neexistujúcu) afiliáciu
{
  "event_type": "TransferRequested",
  "aggregate_id": "affil-new-123",
  "correlation_id": "transfer-456",
  "data": {
    "person_id": "person-abc",
    "from_organization_id": "club-A",
    "to_organization_id": "club-B",
    "effective_date": "2026-07-01"
  }
}

// 2) AffiliationTerminated na pôvodnej afiliácii
{
  "event_type": "AffiliationTerminated",
  "aggregate_id": "affil-old-789",
  "correlation_id": "transfer-456",
  "data": { "reason": "transfer" }
}

// 3) AffiliationRegistered + AffiliationActivated na novej afiliácii
{
  "event_type": "AffiliationRegistered",
  "aggregate_id": "affil-new-123",
  "correlation_id": "transfer-456",
  "data": { /* nová afiliácia v klube B */ }
}

Okrem zachovania agregátových hraníc má saga dve praktické výhody. Po prvé, auditovateľnosť — dotaz typu "ukáž mi celý priebeh tohto prestupu" je filter nad event storom podľa jedného correlation_id. Po druhé, kompenzácia — ak sa v poslednom kroku niečo pokazí (napríklad zväz nová afiliáciu odmietne), systém vygeneruje kompenzačné eventy, ktoré obnovia pôvodný stav. Nevracia sa do minulosti rušením udalostí, ale zapíše ďalšie, ktoré pôvodný stav znovu nastolia — všetko ostáva v histórii.

10 · Hraničné prípady

Čo treba vyriešiť od začiatku

Maloletí

Potrebujú legal_guardian vzťah (Person ↔ Person). Súhlasy udeľuje zákonný zástupca do 18 rokov. Pri dovŕšení plnoletosti systém vyzýva na opätovné potvrdenie súhlasov.

Cudzinci

Nemajú rodné číslo. Používajú foreign_id_type + foreign_id_value + foreign_id_country. Deduplikácia cez meno + DOB + typ a hodnota identifikátora.

Zmeny mena

História mien je samostatná entita PERSON_NAME_HISTORY s časovou platnosťou. Aktuálne meno je "posledné platné".

Úmrtia

Notifikácia z RFO → automatické generovanie AffiliationTerminated eventov pre všetky aktívne afiliácie.

Duplicitné identity

Identity Resolution: fuzzy match + manuálny review + merge operácia. Dva person_id sa zlúčia cez PersonsMerged event.

Konflikt záujmov

Rozhodca má aktívnu afiliáciu v jednom z hrajúcich klubov? Systém to deteguje cez dotaz nad current_affiliations. Upozornenie, nie blokovanie.

· · ·

Nasledujúca kapitola — Číselníky a katalógy — obsahuje zoznam športov, druhov činnosti, typov organizácií a katalóg športovísk.