DI, SOLID i Design Patterns
Dependency Injection, zasady SOLID, GoF wzorce projektowe i Clean Architecture w TypeScript i Node.js.
6 wzorców wstrzykiwania zależności
Constructor Injection, Inversify, Pure DI, Service Locator, Factory i Repository — od najprostszych po zaawansowane kontenery DI.
| Wzorzec | Opis | Zalety | Kiedy |
|---|---|---|---|
| Constructor Injection | Zależności przez konstruktor | Explicit, immutable, easily testable | Standard — używaj domyślnie |
| DI Container (Inversify) | Automatyczna rozwiązywanie grafów zależności | Automatic wiring, scopes, lifecycle | Duże projekty, enterprise, NestJS-like |
| Pure DI (manual) | Ręczne tworzenie i przekazywanie zależności | Zero dependencies, explicit, simple | Małe projekty, frameworki bez DI |
| Service Locator (anti-pattern) | Globalny rejestr zależności | Convenience | Unikaj — ukrywa zależności, trudny do testowania |
| Factory Pattern | Fabryka tworzy odpowiednie implementacje | Decoupling creation from usage | Gdy typ zależy od runtime warunków |
| Repository Pattern | Abstrakcja nad warstwą danych | Zamienne implementacje (DB, in-memory) | Aplikacje z bazą danych, TDD |
Często zadawane pytania
Co to jest Dependency Injection (DI) i dlaczego poprawia testowalność?
Dependency Injection: wzorzec projektowy gdzie zależności są wstrzykiwane z zewnątrz zamiast tworzone wewnątrz klasy. Problem bez DI: class UserService {private db = new PostgresDB(). private logger = new ConsoleLogger(). constructor() {}}. UserService jest ściśle powiązany z PostgresDB i ConsoleLogger. Nie możesz podmienić DB na czas testów. DI solution: class UserService {constructor(private db: Database, private logger: Logger) {}}. Kto tworzy zależności? DI Container lub ręcznie (Pure DI). Zalety DI: testowalność (mock zależności w testach). Wymienność implementacji. Single Responsibility. Loose coupling. Open/Closed Principle. Rodzaje wstrzykiwania: Constructor injection (polecany): zależności przez konstruktor. Property injection: zależności przez property (mniej eksplicytny). Method injection: zależności przez metodę (rzadko). Interfaces jako kontrakty: interface Database {query(sql: string): Promise. insert(table, data): Promise}. class PostgresDB implements Database {...}. class InMemoryDB implements Database {...}. UserService zależy od interfejsu, nie implementacji. Testy: const mockDb = {query: jest.fn(), insert: jest.fn()}. new UserService(mockDb, mockLogger). Bez DI: UserService impossible to mock bez monkey patching.
Inversify — DI container dla TypeScript, jak działa?
InversifyJS: DI container dla TypeScript/JavaScript. Dekoratory (reflect-metadata). Tokens (Symbol lub string). @injectable() dekorator na klasy. @inject(TYPES.Database) w konstruktorze. TYPES: Symbol identyfikatory. const TYPES = {Database: Symbol.for('Database'), Logger: Symbol.for('Logger')}. Binding: const container = new Container(). container.bind(TYPES.Database).to(PostgresDB). container.bind(TYPES.Logger).to(ConsoleLogger). container.bind(TYPES.UserService).to(UserService). Resolve: container.get(TYPES.UserService) — zwraca skonfigurowaną instancję. Scopes: InRequestScope() — per request (HTTP). InSingletonScope() — jedna instancja. InTransientScope() — nowa instancja każdy raz. Middleware: container.applyMiddleware(logger). InversifyExpressUtils: @controller('/users'). @httpGet('/:id'). @httpPost('/') z Express routing. NestJS wbudowany DI: nie potrzebujesz Inversify. @Injectable() — zarejestruj provider. @Inject(TOKEN) — wstrzyknij. Scope: DEFAULT (per module), REQUEST, TRANSIENT. Tsyringe (Microsoft): lekki DI dla TypeScript. container.register(). @injectable(). @inject(). Mniej opinioned niż NestJS. Awilix: functional DI. bez dekoratorów. Rejestracja przez naming convention. Dobre dla projektów bez dekoratorów.
SOLID principles w TypeScript — jak stosować w praktyce?
SOLID: pięć zasad dobrego OOP (Robert C. Martin). Single Responsibility Principle (SRP): klasa/moduł ma jeden powód do zmiany. UserService tylko logika użytkownika. UserRepository tylko DB access. EmailService tylko wysyłanie maili. Każda ma jeden job. Open/Closed Principle (OCP): otwarta na rozszerzenie, zamknięta na modyfikację. Zamiast modyfikować PaymentService dla nowego gateway — dodaj nową klasę implementującą IPaymentGateway. StripeGateway, PaypalGateway, P24Gateway. Liskov Substitution Principle (LSP): podtyp może zastąpić typ bazowy bez psucia programu. Jeśli Bird ma fly(), a Penguin extends Bird ale nie lata — LSP violation. Rozwiązanie: FlyingBird vs NonFlyingBird. Interface Segregation Principle (ISP): wiele małych interfejsów lepsze niż jeden duży. Zamiast IWorker {work(), eat(), sleep()}: IWorker {work()} + IEater {eat()}. Klasy implementują tylko to co potrzebują. Dependency Inversion Principle (DIP): zależności od abstrakcji (interfejsów), nie konkretnych klas. High-level UserService nie powinien znać PostgresDB. Tylko interfejs IUserRepository. TypeScript zastosowanie: interface Repository. class PrismaUserRepository implements Repository. class InMemoryUserRepository implements Repository (testy). UserService(private repo: Repository). Testowanie SOLID kodu: łatwiejsze mockowanie. Wyraźne granice modułów. Niezależne testy jednostkowe.
Design patterns w TypeScript — GoF patterns dla Node.js?
Creational Patterns: Singleton: jedna instancja globalnie. class Database {private static instance: Database. static getInstance(): Database {if (!Database.instance) Database.instance = new Database(). return Database.instance}}. Używaj z ostrożnością (testowanie trudne). Factory Method: klasa decyduje o tworzeniu obiektów. abstract class PaymentFactory {abstract createGateway(): PaymentGateway. processPayment(amount: number) {const gateway = this.createGateway(). gateway.charge(amount)}}. Builder: krok po kroku buduj złożone obiekty. QueryBuilder — fluent interface. new QueryBuilder().select('*').from('users').where('active = true').build(). Structural Patterns: Adapter: dostosuj interfejs do oczekiwanego. StripeAdapter implements PaymentGateway. Opakowuje zewnętrzny SDK. Decorator: dodaj zachowanie bez modyfikacji. logger.info() -> decorated z timestamp, context. Repository Pattern (już omówiony). Behavioral Patterns: Observer (EventEmitter): subscribe/unsubscribe. EventBus — publish/subscribe. Strategy: interchhangeable algorytmy. SortStrategy — różne algorytmy sortowania. PaymentStrategy — różne metody płatności. Command: enkapsuluj operację jako obiekt. CommandBus. execute(command). Undo/redo możliwe. Chain of Responsibility: middleware chain. Każdy handler decyduje czy obsłużyć lub przekazać. Express middleware = CoR. NestJS w kontekście wzorców: Strategy — Guards, Pipes. Command — CQRS module. Observer — EventEmitter, mikroserwisy. Adapter — HTTP adapters (Express, Fastify).
Clean Architecture i Hexagonal Architecture w TypeScript?
Clean Architecture (Robert Martin): koncentryczne kręgi zależności. Wewnętrzne nie wiedzą o zewnętrznych. Entities (core) — Domain objects. Biznesowe reguły. Brak zewnętrznych zależności. Use Cases (application) — logika aplikacji. Orchiestruje Entities. Brak frameworka. Interface Adapters — Controllers, Presenters, Gateways. Konwertują dane między Use Cases a zewnętrznymi. Frameworks & Drivers — Express, NestJS, Prisma, PostgreSQL. Dependency Rule: zależności wskazują do środka (nigdy na zewnątrz). Frameworks mogą zależeć od Use Cases. Use Cases NIE mogą zależeć od Express. Hexagonal Architecture (Ports and Adapters): Ports: interfejsy (wejściowe i wyjściowe). Adapters: implementacje portów. Application core: nie zależy od żadnego adaptera. Primary Adapter: HTTP Controller (incoming). Secondary Adapter: DB Repository (outgoing). Implementacja TypeScript: src/domain/ — entities, value objects. src/application/ — use cases, interfaces. src/infrastructure/ — adapters (Express routes, Prisma repos). src/presentation/ — controllers. Zalety: testowalność (mock adaptery). Framework agnostic core. Wymiana DB bez zmiany Use Cases. Wady: boilerplate. Over-engineering dla małych projektów. Kiedy stosować: duże projekty enterprise. Długie utrzymywanie. Skomplikowana domena. Alternatywa: modular monolith z jasnym podziałem modułów.
Powiązane artykuły
Skontaktuj się z nami
Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.
Wyślij zapytanie
Telefon
+48 790 814 814
Pon-Pt: 9:00 - 18:00
adam@fotz.pl
Odpowiadamy w ciągu 24h
Adres
Plac Wolności 16
61-739 Poznań
Godziny pracy
Wolisz porozmawiać?
Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.
Zadzwoń teraz