TypeScript / Architecture

    DI, SOLID i Design Patterns

    Dependency Injection, zasady SOLID, GoF wzorce projektowe i Clean Architecture w TypeScript i Node.js.

    Inversify
    DI Container
    SOLID
    5 zasad OOP
    Gang of Four
    GoF Patterns
    Clean Arch
    Architektura

    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.

    Czytaj dalej

    Powiązane artykuły

    Kontakt

    Skontaktuj się z nami

    Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.

    Wyślij zapytanie

    Bezpłatna wycena w 24h
    Bez zobowiązań
    Indywidualne podejście
    Ekspresowa realizacja

    Telefon

    +48 790 814 814

    Pon-Pt: 9:00 - 18:00

    Email

    adam@fotz.pl

    Odpowiadamy w ciągu 24h

    Adres

    Plac Wolności 16

    61-739 Poznań

    Godziny pracy

    Pon - Pt9:00 - 18:00
    Sob - NdzZamknięte

    Wolisz porozmawiać?

    Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.

    Zadzwoń teraz