LydiaCMS jsem od začátku nechtěl stavět jako jeden pevně propojený balík funkcí. Potřeboval jsem systém, jehož části můžu vyvíjet, vydávat a případně použít i v jiném projektu. Proto jsem ho rozdělil do samostatných Composer balíčků, které se do aplikace zapojují prostřednictvím Nette DI.
Výsledkem je modulární monolit: za běhu jde stále o jednu aplikaci s jednoduchým provozem, ale její části mají vlastní repozitář, verzi a jasně popsané závislosti.
Jak architektura vznikala
První impuls přišel od kamaráda, který mě požádal o vytvoření webu s fórem. Projekt nakonec nepokračoval, ale zůstal po něm použitelný základ s uživateli a stránkami. Na něm jsem začal stavět obecnější CMS, který jsem mohl využít i pro další weby.
Prvním výrazně odlišným použitím byl Cluesport.com, pokus o metavyhledávač letenek, vstupenek a plánovaného ubytování na zahraniční fotbalová utkání. Z veřejného API jsem importoval zápasy, týmy, města a stadiony, z dalšího API nabídky letenek a data o vstupenkách jsem získával scrapingem. Ubytování zůstalo pouze v plánu.
Největší problém nebyl v samotném stažení dat, ale v jejich propojení a normalizaci napříč zdroji. Právě kvůli této potřebě vznikl samostatný balíček lydiacms-integration. Cluesport zároveň přinesl požadavky na vícejazyčnost a API.
Jak je aplikace rozdělená
LydiaCMS se skládá z 19 samostatně verzovaných Composer balíčků. Nejdůležitější z nich lze rozdělit do tří skupin:
- Core zajišťuje spuštění aplikace, napojení na Doctrine, routování, oprávnění, lokalizaci a základ administrace.
- Blog, Menu, Forms a Socials přidávají konkrétní funkce pro práci s obsahem.
- Data, Integration, API a Backup řeší datové struktury, importy, programové rozhraní, zálohování a obnovu.
Každý balíček má vlastní repozitář a verzi. Mohu ho proto vyvíjet a vydávat samostatně; aplikace si přes Composer skládá konkrétní kombinaci verzí.
Zapojení modulů pomocí Nette DI
Každý modul je samostatný Composer balíček s vlastním rozšířením pro Nette DI. Rozšíření při sestavení kontejneru registruje služby modulu a jeho napojení na Doctrine. Podle potřeby přidává také routy, konzolové příkazy a další části aplikace. Zapojení modulu proto nevyžaduje ruční skládání jeho komponent v bootstrapu.
Administrace z metadat Doctrine
LydiaCMS obsahuje více než sto Doctrine entit. Psát pro každou z nich samostatně formulář, tabulkový přehled a filtry by znamenalo velké množství opakujícího se kódu. Z metadat Doctrine ORM proto generuji základní administrační rozhraní automaticky.
Vlastní komponenty vytvářím pouze tam, kde generované rozhraní nestačí — například pro stromovou správu menu nebo builder formulářů. Menší odlišnosti, jako nápovědu, placeholder nebo způsob vykreslení pole, nastavuji pomocí atributů u datového modelu.
Vícejazyčný obsah a stabilní adresy
Lokalizovaný záznam může mít samostatnou variantu pro každý jazyk. SEO údaje, jako jsou titulek, popis, obrázek a slug, se nastavují pro konkrétní jazykovou variantu, aniž by bylo nutné pro každý jazyk měnit databázový model.
Celá cesta stránky se skládá z jejího umístění ve stromu. Pokud se slug nebo umístění změní, původní adresa se uchová jako trvalé přesměrování na novou. Odkazy z vyhledávačů nebo externích webů tak nekončí chybou.
Jednotný model oprávnění
Administrace i API používají stejný strom kontextů a stejná ACL pravidla. Oprávnění lze nastavit pro celý balíček, jeho konkrétní část nebo jediný záznam. Podřízené kontexty pravidla dědí a mohou je zpřesnit, takže nový modul nemusí vytvářet samostatný systém autorizace.
Pravidla rozlišují operace, například zobrazení, vytvoření, úpravu, smazání, změnu stavu nebo export, a také prostředí administrace, frontendu a API. Stejný mechanismus proto omezuje i správu obsahu pomocí AI.
Co toto rozdělení přináší a co stojí
- Projekt instaluje pouze moduly, které skutečně používá.
- Každý modul lze verzovat a vydávat vlastním tempem.
- Databázový model, administrace a oprávnění používají napříč moduly stejné principy.
- Závislosti mezi balíčky jsou explicitně popsané v Composeru.
Tato architektura zachovává jednoduchý provoz jedné serverové aplikace. Cenou je nutnost hlídat kompatibilitu verzí, udržovat jasné hranice mezi společným jádrem a moduly a průběžně řešit režii vlastního balíčkového ekosystému.
Co bych dnes navrhl jinak
Jednu věc bych dnes oddělil dříve: základní vlastnosti entit bych navrhl tak, aby je bylo možné snadněji nahrazovat a rozšiřovat bez zásahů do pevně provázaného jádra. Právě tento problém mě vedl k práci na balíčku lydiacms-data a k většímu zájmu o agnostické datové modely a Metadata-Driven Architecture.
Dnes bych LydiaCMS směřoval k volnější, více API-first a headless architektuře. Jednotlivé části by měly jít snadněji měnit nebo nahradit a vlastní řešení by mělo vznikat hlavně tam, kde dostupné komponenty nestačí konkrétním požadavkům.