Dependency Injection

Vkladanie závislostí

Milan Herda

November 2025

  • DI
  • IoC
  • DIP
  • Dependency Injection
  • Inversion of Control
  • Dependency Inversion Principle

O čom budeme rozprávať

  • podobá sa na iné C-čkovské jazyky
  • vyzerá ľahký na naučenie
  • treba investovať veľa času na zorientovanie sa v ekosystéme
  • veľa sa toho deje
  • nezostáva čas sa zamyslieť nad architektúrou

Prečo o tom budeme rozprávať

JavaScript má problémy

DI na vedľajšej koľaji

// src/services/userRepository.ts

export async function getCurrentUser(): Promise<User> {
    // ...	
}

Netušíme

Nie je to napísané v jej podpise a nemá dokumentačný komentár

Čo táto funkcia potrebuje k svojmu behu?

// src/services/userRepository.ts

export async function getCurrentUser(): Promise<User> {
	const db = createDbConnection("mysql://user:password@localhost:3306/mydb");

	const rows = await db
		.select("*")
		.from("users")
		.where("id = :id", { id: session.userId })
		.execute<UserData>();

	if (rows.length === 0) {
		throw new Error("User not found");
	}

	const userData = rows[0];

	return {
		id: userData.id,
		name: userData.name,
		surname: userData.surname,
		email: userData.email,
		getFullName() {
			return `${this.name} ${this.surname}`;
		},
	};
}

Čo táto funkcia potrebuje k svojmu behu?

DB Query objekt

Session

Logiku pre vytvorenie User objektu

Cudziu factory funciu

DSN string

Funkcia tieto objekty a functionalitu potrebuje

Vyžaduje ich, aby mohla fungovať správne

Závisí na nich

Sú to jej závislosti

Dependency Injection

// src/services/userRepository.ts

export async function getCurrentUser(): Promise<User> {
    // ...
}
  • nevieme, čo všetko potrebujeme skôr, než funkciu zavoláme
  • prepoužitie je problematické
    • ťažké povedať, čo potrebuje na fungovanie
  • podobný kód môže byť zduplikovaný v iných funkciách
  • aj zmena, ktorá sa netýka primárnej zodpovednosti sa musí robiť v tele tejto funkcie

Je problém, keď závislosti nevidíme

// src/services/userRepository.ts

export async function getCurrentUser(): Promise<User> {
	const db = createDbConnection("mysql://user:password@localhost:3306/mydb");

	const rows = await db
		.select("*")
		.from("users")
		.where("id = :id", { id: session.userId })
		.execute<UserData>();

	if (rows.length === 0) {
		throw new Error("User not found");
	}

	const userData = rows[0];

	return {
		id: userData.id,
		name: userData.name,
		surname: userData.surname,
		email: userData.email,
		getFullName() {
			return `${this.name} ${this.surname}`;
		},
	};
}

Táto funkcia

Čo má kontrolu nad závislosťami?

A keď majú kontrolu funkcie, ...

...neznamená to zároveň,...

...že moja aplikácia...

nemá kontrolu?

Naše aplikácie sú zložené z množstva funkcí

  • vezmeme kontrolu z malých jednotiek a dáme ju veľkým

Inversion of Control

  • funkcia si nebude závislosti manažovať sama
  • požiada o ne
  • nestará sa o pôvod závislostí
    • len dá vedieť volajúcemu, čo potrebuje

Funkcia požiada klientov (volajúci kód), aby jej odovzdali (vložili, inject) tieto závislosti

Inversion of Control

Dependency Injection

// src/services/userRepository.ts

export async function getCurrentUser(db: DbQuery, userId: number): Promise<User> {
	const rows = await db
		.select("*")
		.from("users")
		.where("id = :id", { id: userId })
		.execute<UserData>();

	if (rows.length === 0) {
		throw new Error("User not found");
	}

	const userData = rows[0];

	return {
		id: userData.id,
		name: userData.name,
		surname: userData.surname,
		email: userData.email,
		getFullName() {
			return `${this.name} ${this.surname}`;
		},
	};
}

Čo s touto "závislosťou" na algoritme?

Presun závislostí do parametrov

// src/services/userRepository.ts

export async function getCurrentUser(db: DbQuery, userId: number, createUser: UserFactory): Promise<User> {
	const rows = await db
		.select("*")
		.from("users")
		.where("id = :id", { id: userId })
		.execute<UserData>();

	if (rows.length === 0) {
		throw new Error("User not found");
	}

	return createUser(rows[0]);
}

// src/services/userFactory.ts

export type UserFactory = (data: UserData) => User;

function createUser(data: UserData): User {
	return {
		id: userData.id,
		name: userData.name,
		surname: userData.surname,
		email: userData.email,
		getFullName() {
			return `${this.name} ${this.surname}`;
		},
	};
}

Extrakcia skrytej závislosti

Aplikujeme

Single Responsibility Principle

A extrahujeme algoritmus do vlastnej funkcie a súboru

Použijeme základné techniky

SOLID princípy

// src/somewhere/else.ts

import session from './services/session';
import createUser from './services/userFactory';
import { getCurrentUser } from './services/userRepository';

const db = createDbConnection("mysql://user:password@localhost:3306/mydb");

const currentUser = getCurrentUser(db, session.userId, createUser);

Vkladanie závislostí

// src/services/userRepository.ts

export async function getCurrentUser(
    db: DbQuery,
    userId: number,
    createUser: UserFactory
): Promise<User> {
	const rows = await db
		.select("*")
		.from("users")
		.where("id = :id", { id: userId })
		.execute<UserData>();

	if (rows.length === 0) {
		throw new Error("User not found");
	}

	return createUser(rows[0]);
}

Čo keby sme kód napísali takto?

// src/services/userRepository.ts

import db from './.../dbConnection';
import createUser from './.../userFactory';

export async function getCurrentUser(
    userId: number
): Promise<User> {
	const rows = await db
		.select("*")
		.from("users")
		.where("id = :id", { id: userId })
		.execute<UserData>();

	if (rows.length === 0) {
		throw new Error("User not found");
	}

	return createUser(rows[0]);
}

Kód nám takto síce funguje, ale je tam problém

Funkcia teraz závisí na konkrétnej implementácii pre databázovú query a továrničku pre používateľa

V prípade, že by sme ich chceli vymeniť, musíme zasiahnúť do kódu userRepository

  • piaty zo SOLID princípov
  • učí nás, že máme závisieť na abstrakciách
  • náš kód sa tak stáva viac konfigurovateľný
  • pretože je oveľa jednoduchšie vymeniť abstrakciu, než konkrétnu implementáciu

Dependency Inversion Principle

Námietka: ako často potrebuješ vymeniť jednu implementáciu za inú?

Takmer vždy :)

Pretože dobrý programátor píše testy

Ktorý kód sa bude ľahšie unit-testovať?

// src/services/userRepository.ts

export async function getCurrentUser(
    db: DbQuery,
    userId: number,
    createUser: UserFactory
): Promise<User> {
	const rows = await db
		.select("*")
		.from("users")
		.where("id = :id", { id: userId })
		.execute<UserData>();

	if (rows.length === 0) {
		throw new Error("User not found");
	}

	return createUser(rows[0]);
}
// src/services/userRepository.ts

import db from './.../dbConnection';
import createUser from './.../userFactory';

export async function getCurrentUser(
    userId: number
): Promise<User> {
	const rows = await db
		.select("*")
		.from("users")
		.where("id = :id", { id: userId })
		.execute<UserData>();

	if (rows.length === 0) {
		throw new Error("User not found");
	}

	return createUser(rows[0]);
}

Musíme mockovať moduly

Žiadne mockovanie modulov

Inversion of Control hovorí o zobraní kontroly z malých jednotiek a jej presunu na tie väčšie (aplikácia, framework)

Dependency Injection je o poskytovaní závislostí funkciám, objektom a modulom. Je to v podstate len odovzdávanie argumentov.

Dependency Inversion Principle nás učí, aby sme záviseli na abstraktných veciach (interface, typ) namiesto konkrétnych implementácií

Čo sme sa dnes naučili

Pokiaľ zodpovedne vo svojom kóde aplikujem DI, IoC a DIP, tak... 

Keď si všetky funkcie len pýtajú závislosti cez parametre, tak niečo niekde musí byť ultimatívne zodpovedné za ich vytváranie a manažment.

Service Container

Možno niekedy nabudúce

Kde to má koniec?

Ďakujem za pozornosť

Milan Herda

  • programátor
  • Alma Career (Profesia)
    • Lead Engineer
    • Vedúci "Expertného tímu pre frontend a JavaScript"
  • školím a vzdelávam
  • programujem webové hry
  • mám rád spoločenské hry
  • https://perunhq.org/