Krocení monolitických Bundles v Symfony

posted 25.03.2016

Bundle je základní stavební jednotka Symfony aplikace. Mají doporučené a rozšířené rozvrhnutí složek, auto-konfiguraci, a pokud se správně navrhnou, stačí přidat jeden řádek do composer.json a druhý do AppKernel a rozšíření bězí samo.

Popíšu dva tipy jak rozdělit Bundles v Symfony aplikaci tak, aby se daly na sobě nezávisle rozvíjet a použít napříč více projektama.

Jedna Bundle vládne všem

Následující struktura je docela častým úkazem v menších až středně velkých Symfony aplikacích:

src
└── SymfonyApp
    ├── CoreBundle
    ├── ApiBundle
    └── FrontBundle

Motivací bylo rozdělit API a Frontend tak, aby závisely na společném jádře. Frontend bude obsahovat twig šablony a controllery vykreslující statický front-end a v API Bundle bude vše co se týká serializace pro RESTové API. Obě budou šahat do společného kódu, kde je sdílená logika.

       +---------+       
    +-->  Core   <--+    
    |  +---------+  |    
    |               |    
 +--+----+     +----+--+ 
 |  API  |     | Front | 
 +-------+     +-------+ 

Jak se tak stává, jednoho dne přijde zapeklitý ticket, kde by se na frontendu hodila drobná věc z API.

Se slovy “jen teď jednou a později to opravíme” se najednou vytvořila další závislost.

Hodně častou příčinou podobného slévání jsou třeba Doctrine entity nebo vlastní implementace rozhraní pro uživatele.

      +---------+
   +-->  Core   <--+
   |  +---------+  |
   |               |
+--v----+     +----+--+
|  API  <-----+ Front |
+-------+     +-------+

Podobný cyklus se opakuje ještě nekolikrát, šipky se objeví v obou směrech a nezbývá než aplikaci slít do jedné Bundle.

+-------+
|  App  |
+-------+

Paradoxně, pokud se začtete do Symfony Best Practices, tak je to tam výslovně doporučeno. WTF?

Dává to smysl. Netrávíte čas vymýšlením jak předstírat, že části aplikace jsou nezávislé (a pak trávit čas tím předstíráním a diskuzí o tom). Je to pragmatický přístup, který bude fungovat pro většinu menších webů.

Znovupoužitelné mikrobundles

První tip se nevylučuje s velkou AppBundle, dokonce začíná podobně. Cílem bude z velkého monolitu odštepit malé nezávislé části.

Hlavním rozdílem bude to, že místo hledání řezu na přibližně stejně velké části se budou hledat drobnosti, které lze jednodušše oddělit.

Bundle nevyužije všechny složky, které jsou doporučené v adresářové struktuře. Nebude mít controllery, nebude mít šablony.

Pravidla:

  • Bundle má jen jeden přesně definovaný úkol
  • Bundle nesmí použít kód ze žádné další Bundle
  • tím pádem lze kdykoliv oddělit do vlastního repozitáře
  • nesmí používat žádné skryté závislosti jako např. globální parametr z DI kontejneru. Místo parametru musí použít sémantickou konfiguraci

Pro příklad dávám jednu z nejměnších Bundle z mých projektů:

TimeBundle/
├── DependencyInjection
│   ├── Configuration.php
│   └── TimeExtension.php
├── EventSubscriber
│   └── KernelDefaultTimezoneEventSubscriber.php
├── TimeBundle.php
└── Resources
    └── config
        └── services.yml

Jediným úkolem kódu je nastavit časové pásmo pro celý běh kódu při inicializaci Kernelu.

Správné časové pásmo zjistí z config.yml a předá ho do event subscriberu, kde se zavolá date_default_timezone_set.

Čím menší je komponenta, tím jednodušší je jí oddělit.

Integrace samostatných knihoven

Tyto tři Bundles jsou jedny z nejpopulárnějších v Symfony světe a mají něco společného.

Místo samotné implementace obsahují jen glue code, který integruje knihovnu se Symfony.

Tím kódem můžou být definice služeb, sémantická konfigurace nebo event listenery, které se automaticky napojí na životní cyklus Kernelu. U samotné knihovny pak není nutně potřeba, aby kód s business logikou byl svázaný se Symfony koncepty.

Integrační Bundles se mi osvědčily, když jsem měl ve firmě jak legacy aplikaci v ZF1 a novější v Symfony2. U obou jsme potřebovali použít např. knihovnu pro načítání textů z webového rozhraní pro překladatele, knihovnu pro konzumaci front nebo třeba klientské SDK pro jiné společně užíváné API.

V Zendu jsme knihovnu použili přímo a staticky ji nainicializovali. U Symfony jsme vytvořili repozitář navíc, který celé rozhraní zpřístupnil jako kteroukoliv jinou Symfony službu.