Slovník pojmů

Dependency Injection: co to je a proč ji používat

Závislosti třídy jsou viditelné v jejím rozhraní a lze je při testu nebo změně infrastruktury nahradit.

Stručná definice

Třída si nemusí sama vytvářet vše, s čím pracuje.

Závislostí může být repozitář, klient externího API, logger, fronta zpráv nebo odesílač e-mailu. Pokud si třída takový objekt vytvoří uvnitř, pevně se váže na jednu implementaci a hůře se testuje. Při dependency injection dostane potřebný objekt při svém vytvoření.

Jde o návrhový princip, ne o vlastnost pouze Symfony. Framework může dodat DI container, který vytvoření a propojení služeb automatizuje, ale stejného principu lze dosáhnout i bez něj prostým předáním objektu do konstruktoru.

Použití

Kde předávání závislostí pomáhá

Největší přínos má na hranicích mezi aplikační logikou a technickým okolím.

  • import objednávek s klientem marketplace a repozitářem
  • služba pro zásilky s různými implementacemi dopravců
  • use case volaný z HTTP controlleru, CLI příkazu i handleru zprávy
  • test bez skutečného API, databáze nebo fronty
  • výměna infrastruktury na jednom místě konfigurace

Praktický příklad

Import marketplace objednávky

Třída pro import potřebuje načíst externí objednávku a uložit její lokální podobu. Neví ani nemusí vědět, zda klient mluví RESTem a zda repository používá Doctrine. Tyto volby patří do konfigurace aplikace.

interface MarketplaceOrderClient
{
    public function fetch(string $externalId): ExternalOrder;
}

final class ImportMarketplaceOrder
{
    public function __construct(
        private MarketplaceOrderClient $client,
        private OrderRepository $orders,
    ) {
    }

    public function import(string $externalId): void
    {
        $this->orders->save(Order::fromExternal($this->client->fetch($externalId)));
    }
}

Jak funguje

Od deklarace potřeby k sestavení aplikace

Povinné závislosti je nejčitelnější deklarovat přímo v konstruktoru.

  1. Deklarace Třída ve svém konstruktoru uvede, co opravdu potřebuje.
  2. Složení Konfigurace nebo composition root určí konkrétní implementace.
  3. Použití Třída pracuje přes veřejný kontrakt předaného objektu.
  4. Test Test může předat řízený fake, stub nebo mock namísto vzdálené služby.
  5. Změna Technickou implementaci lze změnit bez zásahu do obchodního rozhodování.

Důležité vlastnosti

Constructor injection, container a rozhraní

DI pomáhá jen tehdy, když zpřesňuje skutečné hranice a odpovědnosti.

Constructor injection

Je vhodná pro závislosti, bez nichž objekt nemůže plnit svou úlohu. Objekt nevznikne v částečně nakonfigurovaném stavu. Setter injection se hodí spíše pro skutečně volitelné chování.

DI container a autowiring

Container eviduje, jak služby vytvořit a propojit. Autowiring vyřeší jednoznačné typy; více implementací, hodnoty a výjimky potřebují explicitní konfiguraci.

Rozhraní

Rozhraní je užitečné pro skutečný kontrakt nebo reálně zaměnitelnou implementaci. Vytvářet je pro každou třídu jen kvůli DI přidává šum bez hodnoty.

Service locator

U DI třída závislosti otevřeně deklaruje. Obecný locator či celý container uvnitř třídy je naopak skrývá; úzce vymezený locator je výjimka, ne běžná náhrada.

Malý příklad

Constructor injection v PHP

Use case zná kontrakty, ne konkrétní technické implementace.

MarketplaceOrderClient
Načte externí objednávku; produkce může použít REST klienta, test připravený fake.
OrderRepository
Uloží objednávku; implementace může používat Doctrine nebo jiné úložiště.
Composition root
Místo pro konkrétní vstupní bod, kde se rozhodne, která implementace se použije.
Aplikační služba
Řeší rozhodnutí importu bez přímého vytváření HTTP klienta či připojení k databázi.

Výhody a omezení

DI není automatická dobrá architektura

Přínosy

  • potřeby třídy jsou čitelné přímo z konstruktoru
  • test nemusí otevírat skutečnou síť ani databázi
  • infrastrukturu lze měnit na jednom místě
  • pomáhá rozlišit obchodní pravidlo od technického detailu

Časté chyby

  • třída s mnoha závislostmi místo rozdělení odpovědností
  • rozhraní pro každou interní třídu bez skutečného kontraktu
  • natažení celého containeru do domény, entity nebo controlleru
  • představa, že DI samo vyřeší špatná pravidla, data nebo transakce

Hranice použití

Abstrakce má mít konkrétní důvod.

Pokud přibude druhý marketplace, není nutné měnit jádro importu, jen když oba zdroje skutečně sdílejí smysluplný kontrakt. DI není důvodem předstírat jednotné rozhraní tam, kde se obchodní chování podstatně liší.

Třída s dvanácti povinnými službami bývá spíš signálem, že spojuje příliš mnoho odpovědností. Správnou reakcí není další konfigurace containeru, ale zjednodušení případu použití nebo rozdělení služby.

Na co myslet

Jasné závislosti místo skrytého globálního stavu

Princip se vyplácí ověřovat v testech i při code review.

  • povinné závislosti přes constructor injection
  • výslovná konfigurace pro nejednoznačné implementace
  • rozhraní jen pro reálný kontrakt nebo změnu chování
  • nepředávat celý container do běžné aplikační třídy
  • testy pro podstatné hranice mezi logikou a infrastrukturou

Časté otázky

Co je dependency injection

Je dependency injection totéž co DI container?

Ne. Dependency injection je princip předávání závislostí. DI container je nástroj, který jejich vytváření a propojování může automatizovat.

Musí mít každá služba vlastní rozhraní?

Ne. Rozhraní je vhodné pro významný kontrakt nebo reálně zaměnitelnou implementaci. Pro jednu interní třídu bez takové potřeby jen přidává další vrstvu kódu.

Proč je constructor injection obvykle lepší než setter injection?

Povinné závislosti jsou viditelné a objekt nemůže vzniknout bez nich. Setter se hodí spíše pro skutečně nepovinné chování.

Je service locator vždy chyba?

Ne vždy, úzce omezený locator může být vhodný pro lazy výběr předem deklarovaných služeb. Celý container v běžné aplikační třídě ale obvykle skrývá závislosti a komplikuje testování.

Jak dependency injection používám v praxi

Rozsah vrstev volím podle složitosti problému.

U složitějších částí odděluji aplikační logiku od databáze, HTTP a externích služeb pomocí jasných závislostí a rozhraní.

Zavolejte mi

Zavolám vám následující pracovní den mezi 9:00 a 17:00.

Můžete mi také zavolat rovnou.

+420 605 181 728

Nechte mi telefonní číslo a pošlete žádost o zpětné zavolání.

Odesláním souhlasíte se zpracováním údajů pro vyřízení žádosti.