solid_dependency_inversion 60 Q&As

Solid Dependency Inversion FAQ & Answers

60 expert Solid Dependency Inversion answers researched from official documentation. Every answer cites authoritative sources you can verify.

unknown

60 questions
A

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions. Robert Martin's two rules: (1) High-level modules (business logic) shouldn't depend on low-level modules (infrastructure). (2) Both depend on abstractions (interfaces). Traditional dependency: OrderService (high-level) depends directly on MySQLDatabase (low-level). Inverted: OrderService depends on IDatabase interface, MySQLDatabase implements IDatabase. 'Inversion' means: Instead of high-level importing low-level, low-level implements interface defined by high-level. Dependency flow inverted: high-level defines abstractions, low-level conforms to them. Benefits: Decoupling (swap implementations), testability (mock interfaces), flexibility (database/email/payment provider independence). Example: class OrderService {constructor(db: IDatabase)} - OrderService doesn't know if it's MySQL, Postgres, or MongoDB. Related concepts: Inversion of Control (IoC) broader principle, Dependency Injection (DI) implementation technique, IoC containers automate DI. DIP foundation for plugin architectures, clean architecture, hexagonal architecture.

99% confidence
A

IoC container is a framework that automatically manages object creation, dependency resolution, and lifecycle management. Instead of manually creating dependencies, container handles instantiation and injection based on configuration. Core functionality: (1) Dependency registration - bind abstractions to implementations, (2) Automatic resolution - create objects with their dependencies, (3) Lifecycle management - control singleton vs transient instances. Example: container.register(IDatabase, MySQLDatabase) then container.resolve(OrderService) automatically creates OrderService with MySQLDatabase injected. Benefits: Eliminates manual wiring, centralizes configuration, manages object lifecycles. Common containers: InversifyJS (TypeScript), TSyringe (TypeScript/Microsoft), Spring ApplicationContext (Java), NestJS built-in container, Autofac (.NET). Trade-off: Framework dependency, learning curve, 'magic' can obscure explicit dependencies. Use when: Large applications with many dependencies. Skip for: Simple apps where explicit control preferred.

99% confidence
A

IoC container implements DIP by enforcing dependency on abstractions through registration and automatic resolution. Mechanism: (1) Register abstractions mapped to implementations - container.register(IDatabase, MySQLDatabase), (2) Resolve dependencies automatically - when creating OrderService, container detects IDatabase dependency and injects registered implementation, (3) High-level modules depend only on interfaces defined in code, low-level modules registered to container. Example workflow: Define interface IDatabase in domain layer. Implement MySQLDatabase in infrastructure. Register: container.bind('IDatabase').to(MySQLDatabase). Use: constructor(@inject('IDatabase') db: IDatabase) - OrderService depends on abstraction, container injects concrete class. Result: Business logic has zero direct imports of infrastructure, implementations swappable via container configuration, perfect DIP compliance. Container acts as mediator - high-level defines what it needs (IDatabase), low-level provides implementation (MySQLDatabase), container connects them at runtime. This inverts traditional dependency flow where high-level imported low-level directly.

99% confidence
A

Choose based on project complexity and team preferences. TSyringe (Microsoft, 1M+ weekly downloads): Best for small-medium projects, minimal setup, decorator-based with no explicit bindings. Use @injectable() and container.resolve(), supports auto-injection via reflect-metadata. Pros: Easy learning curve, lightweight (minimal overhead), elegant API, Microsoft backing. Cons: Less extensible, fewer advanced features. Ideal for: Express apps, microservices, teams preferring simplicity. InversifyJS (1.6M+ weekly downloads, 73 stars): Best for large-scale enterprise applications, explicit bindings, highly extensible. Use @injectable() + @inject() + container.bind(), supports advanced features (multi-injection, custom scopes, middleware). Pros: Feature-rich, type-safe, SOLID-focused, extensive documentation. Cons: Steeper learning curve, more boilerplate (explicit bindings). Ideal for: Complex domain logic, large teams, strict architectural requirements. Performance: Both run 1M-6M ops/sec (negligible difference for most apps). Recommendation: Start with TSyringe for speed, migrate to InversifyJS if complexity demands it. NestJS projects: Use built-in DI container (similar to InversifyJS but framework-integrated).

99% confidence
A

Install: npm install tsyringe reflect-metadata. Enable decorators in tsconfig.json: {"compilerOptions": {"experimentalDecorators": true, "emitDecoratorMetadata": true}}. Import reflect-metadata at entry point: import 'reflect-metadata'. Define service: @injectable() class UserService {constructor(private db: Database) {}}. Register (optional for auto-resolution): container.register(Database, {useClass: PostgresDatabase}). Resolve: const service = container.resolve(UserService) - TSyringe auto-detects constructor dependencies. Singleton pattern: @singleton() class ConfigService {} - single instance across app. Token-based injection: container.register('ILogger', {useClass: ConsoleLogger}). Inject: constructor(@inject('ILogger') logger: ILogger). Lifecycle scopes: Singleton (default), Transient (new instance each resolve), Request-scoped (per HTTP request). Best practice: Use @singleton() for stateless services (config, logging), transient for stateful operations. Testing: Override with mocks via container.register(UserService, {useClass: MockUserService}).

99% confidence
A

Install: npm install inversify reflect-metadata. Enable decorators in tsconfig.json: {"compilerOptions": {"experimentalDecorators": true, "emitDecoratorMetadata": true, "types": ["reflect-metadata"]}}. Import: import 'reflect-metadata'; import {Container, injectable, inject} from 'inversify'. Define service: @injectable() class UserRepository {save(user) {...}}. Create container: const container = new Container(). Bind interface to implementation: container.bind('IUserRepository').to(UserRepository). Use symbols for type-safe tokens: const TYPES = {IUserRepository: Symbol.for('IUserRepository')}. Inject: @injectable() class UserService {constructor(@inject(TYPES.IUserRepository) private repo: IUserRepository) {}}. Resolve: const service = container.resolve(UserService). Lifecycle: Singleton - .inSingletonScope(), Transient - default, Request - .inRequestScope(). Advanced: Multi-injection - @multiInject(TYPES.Logger) loggers: ILogger[] binds multiple implementations. Testing: Override bindings via container.rebind(TYPES.IUserRepository).to(MockRepository). Best practice: Define TYPES in separate file, use symbols not strings for compile-time safety.

99% confidence
A

Three types: Constructor Injection, Setter Injection, Interface Injection. Constructor Injection (preferred): Dependencies passed via constructor. Pattern: class OrderService {constructor(private db: IDatabase, private logger: ILogger)} - all dependencies declared upfront. Benefits: Immutable dependencies (set once), required dependencies enforced (compile-time), clear dependency graph. Spring framework advocates constructor injection. Use for: Required dependencies, most cases (default choice). Setter Injection: Dependencies set via setter methods. Pattern: class OrderService {setLogger(logger: ILogger) {this.logger = logger;}}. Benefits: Optional dependencies, reconfigurable at runtime, circular dependency workaround. Use for: Optional dependencies with reasonable defaults, legacy code compatibility. Drawback: Mutable state, no compile-time enforcement. Interface Injection (rare): Dependency interface provides injector method. Pattern: interface IInjectDatabase {injectDatabase(db: IDatabase)}. OrderService implements IInjectDatabase. Drawback: Invasive (many interfaces), complex, framework-specific. Use for: Framework requirements only (rare in modern code). Modern recommendation (2024): Constructor injection for 95% of cases. Setter for optional features. Avoid interface injection unless framework demands it. TypeScript: Constructor injection with readonly for immutability: constructor(private readonly db: IDatabase).

99% confidence
A

Dependency Inversion is design principle, Dependency Injection is implementation technique. DIP (principle): High-level modules depend on abstractions, not concrete implementations. Architectural guideline. DI (pattern): Technique providing dependencies from outside rather than creating them internally. Implementation detail. Relationship: DI enables DIP. Can have DIP without DI (rare), but DI commonly implements DIP. Example without DI: class OrderService {private db: IDatabase = new MySQLDatabase()} - depends on interface (DIP) but creates concrete class internally. No DI. Example with DIP + DI: class OrderService {constructor(private db: IDatabase)} - depends on interface (DIP) AND receives it externally (DI). Benefits combined: Testability (inject mocks), flexibility (swap implementations), loose coupling. IoC (Inversion of Control): Broader principle - framework calls your code instead of you calling framework. DIP is specific form of IoC. Hierarchy: IoC (general principle) → DIP (design principle for dependencies) → DI (implementation pattern) → IoC Container (framework automating DI). Use: Apply DIP for architecture (define interfaces), implement via DI (constructor injection), optionally use IoC container for automation. DIP = what (depend on abstractions), DI = how (inject dependencies).

99% confidence
A

Service Locator retrieves dependencies from central registry, hiding dependencies and violating DIP. Pattern: class OrderService {process() {const db = ServiceLocator.get('IDatabase'); db.save();}} - dependency on IDatabase hidden inside method. Problems: (1) Hidden dependencies - class signature doesn't reveal what it needs. Testing: Can't see required mocks without reading implementation. (2) Dependency on locator itself - every class depends on ServiceLocator (static cling antipattern). (3) Runtime errors - missing dependency fails at runtime, not compile-time. (4) Harder testing - must configure global locator state before tests. (5) Unclear contracts - API doesn't document dependencies. Dependency Injection solution: class OrderService {constructor(private db: IDatabase)} - dependencies explicit in signature. Benefits: Compile-time safety, clear API, no global state, easy mocking. Martin Fowler: 'Choice less important than separating configuration from use.' Nuance: Service Locator valid for: Legacy system migration, coordinating between services, framework constraints. Recommendation (2024): Prefer DI (constructor injection) for clarity and testability. Use Service Locator only when migration path or framework limitation demands it. Modern frameworks (NestJS, Spring) use DI, not Service Locator.

99% confidence
A

Red flags: (1) High-level class imports/uses concrete low-level classes directly. (2) new keyword for infrastructure dependencies (new MySQLDatabase(), new EmailSender(), new S3Storage()). (3) Hard-coded implementation choices in business logic. (4) Can't test without real database/email/filesystem. (5) Changing infrastructure requires modifying business logic. (6) Using Service Locator to retrieve dependencies (hides dependencies). Example violation: class OrderService {private db = new MySQLDatabase(); process(order) {this.db.save(order);}} - OrderService depends on concrete MySQLDatabase. Fix: interface IDatabase {save(data)}. class OrderService {constructor(private db: IDatabase)} - depend on abstraction. class MySQLDatabase implements IDatabase - low-level implements interface defined by high-level. Test: Can you unit test class with mocked dependencies? If no, likely violates DIP. Can you swap implementation without changing business logic? If no, violates DIP. Package/module structure: Business layer should have zero imports from infrastructure layer. Infrastructure imports from business (interfaces). Control flow: UI → Business → Infrastructure. Dependency flow: UI → Business ← Infrastructure (inverted). Dependencies point inward, never outward.

99% confidence
A

Interfaces belong to the client (high-level module), not the implementation (low-level module). This is the 'inversion'. Traditional approach (wrong): Infrastructure layer defines interfaces, business layer uses them. Business depends on infrastructure. Inverted approach (correct): Business layer defines interfaces it needs, infrastructure implements them. Infrastructure depends on business. Package structure: /domain/services/OrderService.ts (uses IDatabase), /domain/interfaces/IDatabase.ts (defines interface), /infrastructure/MySQLDatabase.ts (implements IDatabase, imports interface from domain). Result: Domain layer has zero dependencies on infrastructure. Fully testable with mocks. Infrastructure depends on domain interfaces. Can swap entire infrastructure without touching domain. Interface ownership: Client owns interface - defines contract it needs. Provider implements client's contract. This enables: Hexagonal/Clean Architecture (domain at center, infrastructure at edges), plugin architecture (plugins implement domain interfaces), framework independence (domain unaware of Express, NestJS, etc.), testability (domain tested without infrastructure). Example: OrderService defines IPaymentGateway interface representing what it needs. Stripe/PayPal/Square plugins implement interface. Domain drives interface design, not infrastructure capabilities. DIP inverts control: High-level dictates contract, low-level conforms.

99% confidence
A

NestJS approach (built-in DI): Use @Injectable() decorator to mark providers. Define interfaces in domain layer. Example: interface IUserRepository {findById(id: string): Promise}. Implementation: @Injectable() class MongoUserRepository implements IUserRepository {findById(id) {/* MongoDB logic */}}. Register provider: @Module({providers: [{provide: 'IUserRepository', useClass: MongoUserRepository}]}) - bind interface to implementation. Inject: constructor(@Inject('IUserRepository') private repo: IUserRepository) - NestJS resolves automatically. Module-based organization: Domain module defines interfaces, Infrastructure module provides implementations. InversifyJS approach (lightweight container): Install: npm install inversify reflect-metadata. Enable decorators: experimentalDecorators, emitDecoratorMetadata in tsconfig.json. Define: @injectable() class MySQLDatabase implements IDatabase. Register: container.bind('IDatabase').to(MySQLDatabase). Inject: @inject('IDatabase') private db: IDatabase. Resolve: const service = container.resolve(OrderService). Benefits: SOLID-focused, framework-agnostic, explicit bindings. Modern pattern (2024): Use decorators for metadata, constructor injection for immutability, module/container for organization. Testing: Override bindings with mocks. Example: container.rebind('IDatabase').to(MockDatabase). Recommendation: NestJS for full framework, InversifyJS for Node.js apps needing DI without framework lock-in.

99% confidence
A

Clean Architecture applies DIP to enforce inward dependency flow. DIP is foundational principle, Clean Architecture is architectural application. DIP: High-level modules depend on abstractions, not implementations. Clean Architecture: Concentric circles with dependency rule - source code dependencies point only inward. Inner circles (entities, use cases) define interfaces. Outer circles (interface adapters, frameworks) implement them. Benefits: Entities remain pure business rules (no framework dependencies), Use cases only depend on entity interfaces, Frameworks are replaceable plugins. Without DIP: Clean Architecture impossible - layers would depend outward on frameworks. With DIP: Frameworks depend on application, not vice versa. Example: Use Case defines UserRepository interface, Infrastructure layer implements it. Use Case unaware of PostgreSQL vs MongoDB. DIP enables clean layering, framework independence, testability.

99% confidence
A

Hexagonal Architecture (Ports & Adapters) uses DIP to invert control flow. Domain defines ports (interfaces) representing what it needs. Infrastructure implements adapters that plug into ports. Pattern: Domain center (hexagon) has inbound ports (IUseCase) and outbound ports (IRepository). UI/Controllers implement inbound ports, Database/Email implement outbound ports. DIP inversion: Instead of domain calling infrastructure, infrastructure implements domain's required interfaces. Example: OrderProcessor defines IPaymentGateway port. StripePaymentAdapter implements IPaymentGateway. OrderProcessor calls port.process(), StripeAdapter provides implementation. Benefits: Domain remains pure (no infrastructure), adapters swappable (Stripe → PayPal), business logic testable without external services. Ports = contracts domain needs, Adapters = concrete implementations. DIP is what makes hexagonal architecture possible - domain dictates interfaces, infrastructure conforms.

99% confidence
A

Clean Architecture dependency flow always points inward. Outer layers depend on inner layers, never reverse. Layer order: Entities (innermost) → Use Cases → Interface Adapters → Frameworks/Drivers (outermost). Entities: Pure business rules, no dependencies. Use Cases: Depend only on Entities (application-specific business rules). Interface Adapters: Depend on Use Cases, convert data formats (controllers, presenters). Frameworks: Depend on all inner layers (web, database, external services). Flow: HTTP Request → Controller (Framework) → Presenter (Adapter) → Use Case → Entity. Dependencies flow opposite direction: Controller → Use Case → Entity (inward). Rule: Inner circle knows nothing about outer circles. Outer circles contain mechanisms (web UI, database, external APIs), inner circles contain policies (business rules). Example: Controller calls Use Case interface, Use Case calls Entity methods, Entity never calls Use Case or Controller. Inward flow enables testing: Mock outer layers, test inner layers in isolation.

99% confidence
A

In Hexagonal Architecture, interfaces belong to the domain (inside the hexagon), not infrastructure. Domain defines ports (interfaces) representing what domain needs from outside. Infrastructure implements adapters that conform to these ports. Pattern: Domain/UserManagement.ts defines IPersistencePort, INotificationPort. Infrastructure/PostgresAdapter implements IPersistencePort, EmailAdapter implements INotificationPort. Rule: Ports defined by domain, adapters implemented by infrastructure. Benefits: Domain independent of infrastructure, adapters swappable without changing domain, business logic testable with mock adapters. Example: OrderService defines IOrderRepository interface with save(), findById() methods. PostgreSQL implements OrderRepository adapter, MongoDB implements MongoOrderRepository adapter. Both adapters connect to same domain port. Domain code: constructor(private repo: IOrderRepository) - depends on interface defined in domain, not concrete implementation. Port location: Inside domain boundary, not shared with infrastructure. Interface ownership: Domain owns contracts, infrastructure provides implementations.

99% confidence
A

Constructor Injection passes dependencies via class constructor, providing required dependencies upfront. Pattern: class OrderService {constructor(private db: IDatabase, private logger: ILogger) {}}. All dependencies declared in constructor signature, immutable via readonly/private. Benefits: Compile-time safety (missing dependencies caught early), clear dependency graph (constructor shows all requirements), immutable state (can't change dependencies after creation), easier testing (mock in constructor). Use for: Required dependencies that class can't function without. Modern frameworks (NestJS, Angular, Spring) prefer constructor injection as default. TypeScript: constructor(private readonly db: IDatabase) ensures immutability. Inversion of Control container resolves constructor dependencies automatically. Example: const service = new OrderService(database, logger) - dependencies explicit, no hidden requirements. Avoid when: Dependencies are optional, need runtime resolution, or would cause circular dependencies (use Setter Injection instead).

99% confidence
A

Setter Injection provides dependencies through setter methods after object creation. Pattern: class OrderService {private db: IDatabase; setDatabase(db: IDatabase) {this.db = db;}}. Benefits: Optional dependencies with reasonable defaults, reconfigurable at runtime, resolves circular dependencies. Drawbacks: (1) Mutable state - dependencies can change during object lifetime, (2) Incomplete state - object may exist without required dependencies, (3) Runtime errors - missing dependencies discovered at usage time, not creation time, (4) Hidden dependencies - constructor doesn't reveal what class needs, (5) Thread safety issues - concurrent access during setter calls. Use for: Optional features that can be configured later, legacy code compatibility, resolving circular dependencies. Modern preference: Use Constructor Injection for required dependencies, Setter only for truly optional ones. Framework support: Spring supports setter injection via @Autowired on setters, but recommends constructor injection as primary choice.

99% confidence
A

Interface Injection (classic definition) provides dependencies through interfaces that define injector methods - NOT to be confused with injecting interfaces (modern best practice). Pattern: interface IDatabaseInjector {injectDatabase(db: IDatabase);} class OrderService implements IDatabaseInjector {injectDatabase(db: IDatabase) {this.db = db;}}. Container calls injectDatabase() to provide dependency. Drawbacks: (1) Invasive - classes must implement injection interfaces, (2) Framework-specific coupling, (3) Complex - requires multiple interfaces for multiple dependencies, (4) Largely obsolete in 2025. Historical use: Early Java EE (Avalon framework), legacy .NET containers. Modern consensus (2025): Constructor injection preferred across Spring 6+, NestJS, Angular 17+. Note: 'Injecting interfaces' (depending on abstractions) remains best practice - this Q&A refers specifically to the obsolete injector method pattern. TypeScript limitation: Interfaces disappear at runtime, requiring workarounds (string tokens, abstract classes). Use interface injection only when: Legacy framework requires it, migrating ancient codebases. Otherwise: Use constructor injection with interface dependencies (e.g., constructor(private db: IDatabase)) - this follows DIP without interface injection pattern.

99% confidence
A

Constructor Injection should be the default choice for 95% of cases. Reasons: (1) Compile-time safety - missing dependencies caught immediately, (2) Immutability - dependencies can't change after creation, (3) Clear requirements - constructor shows all needed dependencies, (4) Testability - easy to mock in tests, (5) No hidden state - object fully formed after construction. Constructor injection promotes good design: Required dependencies explicit, object always in valid state, easier to understand and maintain. Use Setter Injection only for: Optional dependencies with reasonable defaults, circular dependencies (rare in good design), legacy system integration. Avoid Interface Injection unless framework demands it. Modern framework consensus: Spring, NestJS, Angular all recommend constructor injection as primary choice. Example: OrderService requires Database and Logger - constructor injection. OrderService optionally uses Cache - setter injection with default NullCache. Rule: Start with constructor injection, use setter only when you have specific reason for optional/reconfigurable dependencies.

99% confidence
A

DIP in React means components depend on abstractions (context, props interfaces) not concrete implementations. Pattern: Context API for dependency injection. Violation: Component imports concrete service directly. class UserList extends Component {componentDidMount() {const api = new UserAPI(); api.fetchUsers().then(setUsers);}} - depends on concrete UserAPI. DIP compliant: Define abstraction via Context. interface IUserService {fetchUsers(): Promise<User[]>} const UserServiceContext = React.createContext(null). Provider injects implementation: <UserServiceContext.Provider value={new APIUserService()}></UserServiceContext.Provider>. Component depends on abstraction: const service = useContext(UserServiceContext); useEffect(() => {service.fetchUsers()}, []). Benefits: Testability (inject mock service), flexibility (swap API implementation), loose coupling. Modern 2025 patterns: (1) Custom hooks as abstractions: function useUsers(): User[] - implementation hidden, consumers depend on hook interface. (2) Render props: <DataProvider render={(data) => } /> - component depends on render prop signature. (3) Higher-order components: withUserService(UserList) - HOC injects dependency. Best practice: Use Context for cross-cutting concerns (auth, theme, i18n), props for direct dependencies, custom hooks for encapsulated logic.

99% confidence
A

Common React DIP violations: (1) Direct API imports in components. import {fetchUsers} from '../api/users'; useEffect(() => {fetchUsers()}, []) - component depends on concrete API module. Fix: Inject via Context or custom hook. (2) Hardcoded services. const [data, setData] = useState(); useEffect(() => {axios.get('/api/users').then(setData)}, []) - depends on axios, hardcoded URL. Fix: useDataService hook abstracting data fetching. (3) localStorage coupling. useEffect(() => {const token = localStorage.getItem('token')}, []) - depends on browser API. Fix: Storage abstraction interface IStorage {get(key), set(key, value)} with implementations LocalStorage, SessionStorage, MockStorage. (4) Router dependency. import {useNavigate} from 'react-router-dom'; navigate('/home') - couples to specific router library. Fix: Navigation abstraction or accept navigate as prop. (5) Global state imports. import {store} from '../store' - couples to concrete store. Fix: Context provider for state. Modern patterns: Custom hooks encapsulate dependencies (useAuth, useAPI, useStorage), making components depend on hook contracts, not implementations. Testing: Components using abstractions easily tested with mocks. const mockService = {fetchUsers: jest.fn()}; <ServiceContext.Provider value={mockService}>.

99% confidence
A

DIP enables testing by allowing mock injection instead of real dependencies. Without DIP: class OrderService {private db = new MySQLDatabase(); process(order) {this.db.save(order);}} - tests require real MySQL database, slow integration tests. With DIP: class OrderService {constructor(private db: IDatabase)} - inject mock in tests. Test setup: const mockDb: IDatabase = {save: jest.fn(), find: jest.fn()}; const service = new OrderService(mockDb); service.process(order); expect(mockDb.save).toHaveBeenCalledWith(order) - fast unit test, no database. Benefits: (1) Fast tests - no I/O, network, or database calls. (2) Isolated tests - test single class behavior without dependencies. (3) Deterministic - mocks return predictable values, no flaky tests. (4) Easy setup - create simple mocks, no complex fixtures. Testing pyramid: Unit tests (mock all dependencies via DI), Integration tests (test with real dependencies), E2E tests (full system). DIP enables unit test coverage at minimal cost. Modern tools: Jest/Vitest mock functions, TypeScript jest.Mocked for type-safe mocks, testing-library with Context providers for React. Pattern: Test doubles via DI - Mocks (verify behavior), Stubs (provide canned responses), Fakes (working implementations like in-memory database). All injected via abstractions defined by DIP.

99% confidence
A

DIP in microservices ensures services depend on abstract contracts (APIs, message schemas) not concrete implementations. Pattern: Service defines interface for external dependencies, adapters implement interface for different backends. Example: OrderService needs PaymentGateway. Define: interface IPaymentGateway {charge(amount): Promise}. OrderService depends on interface. Adapters: StripeAdapter implements IPaymentGateway (calls Stripe API), PayPalAdapter implements IPaymentGateway (calls PayPal API). Configuration determines which adapter to inject. Benefits: (1) Service testable with mock gateway. (2) Switch payment providers without OrderService changes. (3) Multi-gateway support (route by region/amount). Inter-service communication: Define message contracts (Protobuf, JSON Schema), services depend on contracts not implementations. Event-driven: OrderService publishes OrderCreated event to interface IEventBus, infrastructure implements with RabbitMQ, Kafka, or AWS SNS. Service mesh pattern: Services communicate via abstraction layer (Istio, Linkerd), not direct HTTP calls. Modern 2025: gRPC with Protobuf contracts, GraphQL Federation with schema contracts, OpenAPI specs as contracts. Best practice: API versioning for backward compatibility, contract testing (Pact) ensures implementations match contracts, consumer-driven contracts put interface ownership with consumers (DIP).

99% confidence
A

Circular dependencies violate good design - A depends on B, B depends on A. DIP helps resolve via abstraction layers. Problem: class UserService {constructor(private orders: OrderService)} class OrderService {constructor(private users: UserService)} - circular dependency, container fails to resolve. Solution 1 - Extract interface: interface IUserLookup {findUser(id)}. OrderService depends on IUserLookup, UserService implements IUserLookup. Break cycle: OrderService → IUserLookup ← UserService. Solution 2 - Lazy injection (Setter): OrderService uses constructor injection for primary deps, setter injection for circular: setUserService(users: UserService). Delay injection after both constructed. Solution 3 - Event-driven: Remove direct dependency. OrderService publishes OrderCreated event, UserService subscribes. No direct coupling. Solution 4 - Intermediate service: Extract shared logic to new service. OrderUserCoordinator depends on both UserService and OrderService. Neither depends on each other. Solution 5 - Redesign: Circular dependency often indicates wrong boundaries. Move shared concern to separate module both depend on. TypeScript: Use forwardRef() in NestJS, Provider in InversifyJS for lazy resolution. Best practice: Circular dependencies are code smell - indicates missing abstraction or wrong service boundaries. Prefer Solutions 3-4 (eliminate dependency) over Solution 2 (setter workaround).

99% confidence
A

Factory pattern inverts dependency by having high-level code depend on factory interface, low-level code implement concrete factories. Pattern: High-level defines what it needs via factory interface. interface IPaymentProcessorFactory {create(type: string): IPaymentProcessor}. High-level depends on factory: class OrderService {constructor(private factory: IPaymentProcessorFactory)} process(order: Order) {const processor = this.factory.create(order.paymentType); processor.charge(order.amount);}. Low-level implements factory: class ConcretePaymentFactory implements IPaymentProcessorFactory {create(type: string): IPaymentProcessor {switch(type) {case 'stripe': return new StripeProcessor(); case 'paypal': return new PayPalProcessor();}}}. DIP achieved: OrderService depends on abstractions (IPaymentProcessorFactory, IPaymentProcessor), not concrete classes (StripeProcessor, PayPalProcessor). Factory creates concrete classes but OrderService unaware. Benefits: Centralized creation logic, runtime type selection, testable (mock factory returns test doubles). Abstract Factory: Factory for families of related objects. Modern TypeScript: Use DI container instead of manual factory for complex scenarios. container.bind('stripe').to(StripeProcessor). Factory Method pattern: Subclass overrides factory method to change creation logic - also inverts dependency on concrete product classes.

99% confidence
A

DIP in monorepos ensures modules depend on shared abstractions, not concrete implementations from other modules. Architecture: Shared module defines interfaces, domain modules implement interfaces, application modules depend on shared interfaces only. Example structure: /packages/shared/interfaces/IUserRepository.ts - interface definitions. /packages/user-domain/UserService.ts - implements business logic, depends on IUserRepository. /packages/user-infra/MongoUserRepository.ts - implements IUserRepository using MongoDB. Application: import {IUserRepository} from '@shared/interfaces'; import {UserService} from '@user-domain' - depends only on interfaces and domain, not infrastructure. Benefits: (1) Clear module boundaries - domain unaware of infrastructure. (2) Independent development - teams work on different modules without coupling. (3) Testability - domain tested without infrastructure modules. (4) Build optimization - change in infra doesn't rebuild domain. Module dependency graph: Application → Domain → Shared Interfaces ← Infrastructure. Infrastructure depends inward, never outward. Modern tools: Nx, Turborepo enforce dependency constraints via lint rules. nx.json: {"projectDependencies": {"user-domain": ["shared"], "user-infra": ["shared", "user-domain"]}} - prevents domain from importing infra. Best practice: Use barrel exports from shared module, define dependency constraints in monorepo config, architectural tests verify dependency rules (ArchUnit-style).

99% confidence
A

DIP in serverless means Lambda functions depend on abstractions for AWS services, databases, and external APIs - enables local testing and provider independence. Violation: Lambda imports AWS SDK directly. import {DynamoDB} from 'aws-sdk'; export const handler = async (event) => {const db = new DynamoDB(); await db.putItem({TableName: 'Users', Item: event.body});}. Problem: Can't test locally, coupled to DynamoDB. DIP solution: Define interface for data access. interface IUserRepository {save(user: User): Promise}. Lambda depends on abstraction. export const handler = async (event, context, repository: IUserRepository = new DynamoUserRepository()) => {await repository.save(event.body);}. Testing: Inject mock repository. const mockRepo: IUserRepository = {save: jest.fn()}; await handler(event, context, mockRepo). Benefits: Local development with in-memory implementation, unit testing without AWS, multi-cloud support (swap DynamoDB for CosmosDB). Dependency injection in Lambda: (1) Constructor injection via wrapper: const repository = new DynamoUserRepository(); exports.handler = (event) => lambdaLogic(event, repository). (2) Singleton pattern: Lazy initialization outside handler, reused across invocations. let repo: IUserRepository; exports.handler = async (event) => {repo = repo || createRepository(); await repo.save()}. Modern 2025: Middy middleware for DI, AWS Lambda Powertools for observability abstraction, SST (Serverless Stack) for infrastructure abstraction. Best practice: Separate Lambda handler (thin adapter) from business logic (depends on interfaces), test business logic without Lambda runtime.

99% confidence
A

DI containers add minimal overhead but require configuration for optimal performance. Performance factors: (1) Resolution cost - container uses reflection/metadata to resolve dependencies. TSyringe/InversifyJS: 1M-6M operations/sec (negligible for most apps). (2) Lifecycle management - Singleton (resolve once, cache), Transient (create every time, slower), Scoped (per request, middle ground). Recommendation: Use Singleton for stateless services (logging, config), Transient only when state isolation required. (3) Lazy vs eager initialization - Lazy: Resolve on first use (faster startup, slower first request). Eager: Resolve at app startup (slower startup, consistent latency). Production: Use eager for critical path dependencies. (4) Circular dependencies - Container recursion adds overhead. Avoid circular deps via design. Benchmarks (2025): InversifyJS container resolution: 1.6M ops/sec, TSyringe: 5M ops/sec, Manual DI (new): 50M ops/sec. Reality: Difference is 200ns vs 20ns - insignificant compared to I/O (ms). Optimization tips: (1) Prefer singleton scope for services, (2) Use manual DI for hot paths if profiling shows issue, (3) Avoid excessive decorator metadata, (4) Lazy-load heavy dependencies. Bottom line: DI container overhead is negligible. Focus on code clarity and testability, not micro-optimization. Only optimize if profiling proves container is bottleneck (rare).

99% confidence
A

Refactor legacy code to DIP incrementally via Strangler Fig pattern - gradually replace concrete dependencies with abstractions. Step 1 - Identify concrete dependencies: Find new instantiations of external services (new MySQLDatabase(), new EmailService(), import axios). Step 2 - Extract interfaces: Create interface matching existing concrete class. interface IDatabase extends MySQLDatabase {} - start with structural match. Step 3 - Constructor injection: Change class to accept interface. Before: class OrderService {private db = new MySQLDatabase()}. After: class OrderService {constructor(private db: IDatabase = new MySQLDatabase())} - default maintains backward compatibility. Step 4 - Introduce factory/builder: Centralize object creation. class ServiceFactory {static createOrderService(): OrderService {return new OrderService(new MySQLDatabase());}}. Callers use factory, enabling future DI container. Step 5 - Simplify interface: Remove MySQL-specific methods from IDatabase, make truly abstract. Step 6 - Add DI container gradually: Start with new features using container, migrate existing code over time. Testing strategy: Write characterization tests before refactoring, test existing behavior preserved. Add new tests for injected mocks. Sprout pattern: New functionality uses DI from start, old code incrementally refactored. Anti-pattern: Big bang rewrite - refactor gradually. Legacy adapters: Wrap old concrete classes with adapter implementing new interfaces. Modern tools: Approval tests for legacy behavior, code coverage to find untested areas (high risk for refactoring).

99% confidence
A

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions. Robert Martin's two rules: (1) High-level modules (business logic) shouldn't depend on low-level modules (infrastructure). (2) Both depend on abstractions (interfaces). Traditional dependency: OrderService (high-level) depends directly on MySQLDatabase (low-level). Inverted: OrderService depends on IDatabase interface, MySQLDatabase implements IDatabase. 'Inversion' means: Instead of high-level importing low-level, low-level implements interface defined by high-level. Dependency flow inverted: high-level defines abstractions, low-level conforms to them. Benefits: Decoupling (swap implementations), testability (mock interfaces), flexibility (database/email/payment provider independence). Example: class OrderService {constructor(db: IDatabase)} - OrderService doesn't know if it's MySQL, Postgres, or MongoDB. Related concepts: Inversion of Control (IoC) broader principle, Dependency Injection (DI) implementation technique, IoC containers automate DI. DIP foundation for plugin architectures, clean architecture, hexagonal architecture.

99% confidence
A

IoC container is a framework that automatically manages object creation, dependency resolution, and lifecycle management. Instead of manually creating dependencies, container handles instantiation and injection based on configuration. Core functionality: (1) Dependency registration - bind abstractions to implementations, (2) Automatic resolution - create objects with their dependencies, (3) Lifecycle management - control singleton vs transient instances. Example: container.register(IDatabase, MySQLDatabase) then container.resolve(OrderService) automatically creates OrderService with MySQLDatabase injected. Benefits: Eliminates manual wiring, centralizes configuration, manages object lifecycles. Common containers: InversifyJS (TypeScript), TSyringe (TypeScript/Microsoft), Spring ApplicationContext (Java), NestJS built-in container, Autofac (.NET). Trade-off: Framework dependency, learning curve, 'magic' can obscure explicit dependencies. Use when: Large applications with many dependencies. Skip for: Simple apps where explicit control preferred.

99% confidence
A

IoC container implements DIP by enforcing dependency on abstractions through registration and automatic resolution. Mechanism: (1) Register abstractions mapped to implementations - container.register(IDatabase, MySQLDatabase), (2) Resolve dependencies automatically - when creating OrderService, container detects IDatabase dependency and injects registered implementation, (3) High-level modules depend only on interfaces defined in code, low-level modules registered to container. Example workflow: Define interface IDatabase in domain layer. Implement MySQLDatabase in infrastructure. Register: container.bind('IDatabase').to(MySQLDatabase). Use: constructor(@inject('IDatabase') db: IDatabase) - OrderService depends on abstraction, container injects concrete class. Result: Business logic has zero direct imports of infrastructure, implementations swappable via container configuration, perfect DIP compliance. Container acts as mediator - high-level defines what it needs (IDatabase), low-level provides implementation (MySQLDatabase), container connects them at runtime. This inverts traditional dependency flow where high-level imported low-level directly.

99% confidence
A

Choose based on project complexity and team preferences. TSyringe (Microsoft, 1M+ weekly downloads): Best for small-medium projects, minimal setup, decorator-based with no explicit bindings. Use @injectable() and container.resolve(), supports auto-injection via reflect-metadata. Pros: Easy learning curve, lightweight (minimal overhead), elegant API, Microsoft backing. Cons: Less extensible, fewer advanced features. Ideal for: Express apps, microservices, teams preferring simplicity. InversifyJS (1.6M+ weekly downloads, 73 stars): Best for large-scale enterprise applications, explicit bindings, highly extensible. Use @injectable() + @inject() + container.bind(), supports advanced features (multi-injection, custom scopes, middleware). Pros: Feature-rich, type-safe, SOLID-focused, extensive documentation. Cons: Steeper learning curve, more boilerplate (explicit bindings). Ideal for: Complex domain logic, large teams, strict architectural requirements. Performance: Both run 1M-6M ops/sec (negligible difference for most apps). Recommendation: Start with TSyringe for speed, migrate to InversifyJS if complexity demands it. NestJS projects: Use built-in DI container (similar to InversifyJS but framework-integrated).

99% confidence
A

Install: npm install tsyringe reflect-metadata. Enable decorators in tsconfig.json: {"compilerOptions": {"experimentalDecorators": true, "emitDecoratorMetadata": true}}. Import reflect-metadata at entry point: import 'reflect-metadata'. Define service: @injectable() class UserService {constructor(private db: Database) {}}. Register (optional for auto-resolution): container.register(Database, {useClass: PostgresDatabase}). Resolve: const service = container.resolve(UserService) - TSyringe auto-detects constructor dependencies. Singleton pattern: @singleton() class ConfigService {} - single instance across app. Token-based injection: container.register('ILogger', {useClass: ConsoleLogger}). Inject: constructor(@inject('ILogger') logger: ILogger). Lifecycle scopes: Singleton (default), Transient (new instance each resolve), Request-scoped (per HTTP request). Best practice: Use @singleton() for stateless services (config, logging), transient for stateful operations. Testing: Override with mocks via container.register(UserService, {useClass: MockUserService}).

99% confidence
A

Install: npm install inversify reflect-metadata. Enable decorators in tsconfig.json: {"compilerOptions": {"experimentalDecorators": true, "emitDecoratorMetadata": true, "types": ["reflect-metadata"]}}. Import: import 'reflect-metadata'; import {Container, injectable, inject} from 'inversify'. Define service: @injectable() class UserRepository {save(user) {...}}. Create container: const container = new Container(). Bind interface to implementation: container.bind('IUserRepository').to(UserRepository). Use symbols for type-safe tokens: const TYPES = {IUserRepository: Symbol.for('IUserRepository')}. Inject: @injectable() class UserService {constructor(@inject(TYPES.IUserRepository) private repo: IUserRepository) {}}. Resolve: const service = container.resolve(UserService). Lifecycle: Singleton - .inSingletonScope(), Transient - default, Request - .inRequestScope(). Advanced: Multi-injection - @multiInject(TYPES.Logger) loggers: ILogger[] binds multiple implementations. Testing: Override bindings via container.rebind(TYPES.IUserRepository).to(MockRepository). Best practice: Define TYPES in separate file, use symbols not strings for compile-time safety.

99% confidence
A

Three types: Constructor Injection, Setter Injection, Interface Injection. Constructor Injection (preferred): Dependencies passed via constructor. Pattern: class OrderService {constructor(private db: IDatabase, private logger: ILogger)} - all dependencies declared upfront. Benefits: Immutable dependencies (set once), required dependencies enforced (compile-time), clear dependency graph. Spring framework advocates constructor injection. Use for: Required dependencies, most cases (default choice). Setter Injection: Dependencies set via setter methods. Pattern: class OrderService {setLogger(logger: ILogger) {this.logger = logger;}}. Benefits: Optional dependencies, reconfigurable at runtime, circular dependency workaround. Use for: Optional dependencies with reasonable defaults, legacy code compatibility. Drawback: Mutable state, no compile-time enforcement. Interface Injection (rare): Dependency interface provides injector method. Pattern: interface IInjectDatabase {injectDatabase(db: IDatabase)}. OrderService implements IInjectDatabase. Drawback: Invasive (many interfaces), complex, framework-specific. Use for: Framework requirements only (rare in modern code). Modern recommendation (2024): Constructor injection for 95% of cases. Setter for optional features. Avoid interface injection unless framework demands it. TypeScript: Constructor injection with readonly for immutability: constructor(private readonly db: IDatabase).

99% confidence
A

Dependency Inversion is design principle, Dependency Injection is implementation technique. DIP (principle): High-level modules depend on abstractions, not concrete implementations. Architectural guideline. DI (pattern): Technique providing dependencies from outside rather than creating them internally. Implementation detail. Relationship: DI enables DIP. Can have DIP without DI (rare), but DI commonly implements DIP. Example without DI: class OrderService {private db: IDatabase = new MySQLDatabase()} - depends on interface (DIP) but creates concrete class internally. No DI. Example with DIP + DI: class OrderService {constructor(private db: IDatabase)} - depends on interface (DIP) AND receives it externally (DI). Benefits combined: Testability (inject mocks), flexibility (swap implementations), loose coupling. IoC (Inversion of Control): Broader principle - framework calls your code instead of you calling framework. DIP is specific form of IoC. Hierarchy: IoC (general principle) → DIP (design principle for dependencies) → DI (implementation pattern) → IoC Container (framework automating DI). Use: Apply DIP for architecture (define interfaces), implement via DI (constructor injection), optionally use IoC container for automation. DIP = what (depend on abstractions), DI = how (inject dependencies).

99% confidence
A

Service Locator retrieves dependencies from central registry, hiding dependencies and violating DIP. Pattern: class OrderService {process() {const db = ServiceLocator.get('IDatabase'); db.save();}} - dependency on IDatabase hidden inside method. Problems: (1) Hidden dependencies - class signature doesn't reveal what it needs. Testing: Can't see required mocks without reading implementation. (2) Dependency on locator itself - every class depends on ServiceLocator (static cling antipattern). (3) Runtime errors - missing dependency fails at runtime, not compile-time. (4) Harder testing - must configure global locator state before tests. (5) Unclear contracts - API doesn't document dependencies. Dependency Injection solution: class OrderService {constructor(private db: IDatabase)} - dependencies explicit in signature. Benefits: Compile-time safety, clear API, no global state, easy mocking. Martin Fowler: 'Choice less important than separating configuration from use.' Nuance: Service Locator valid for: Legacy system migration, coordinating between services, framework constraints. Recommendation (2024): Prefer DI (constructor injection) for clarity and testability. Use Service Locator only when migration path or framework limitation demands it. Modern frameworks (NestJS, Spring) use DI, not Service Locator.

99% confidence
A

Red flags: (1) High-level class imports/uses concrete low-level classes directly. (2) new keyword for infrastructure dependencies (new MySQLDatabase(), new EmailSender(), new S3Storage()). (3) Hard-coded implementation choices in business logic. (4) Can't test without real database/email/filesystem. (5) Changing infrastructure requires modifying business logic. (6) Using Service Locator to retrieve dependencies (hides dependencies). Example violation: class OrderService {private db = new MySQLDatabase(); process(order) {this.db.save(order);}} - OrderService depends on concrete MySQLDatabase. Fix: interface IDatabase {save(data)}. class OrderService {constructor(private db: IDatabase)} - depend on abstraction. class MySQLDatabase implements IDatabase - low-level implements interface defined by high-level. Test: Can you unit test class with mocked dependencies? If no, likely violates DIP. Can you swap implementation without changing business logic? If no, violates DIP. Package/module structure: Business layer should have zero imports from infrastructure layer. Infrastructure imports from business (interfaces). Control flow: UI → Business → Infrastructure. Dependency flow: UI → Business ← Infrastructure (inverted). Dependencies point inward, never outward.

99% confidence
A

Interfaces belong to the client (high-level module), not the implementation (low-level module). This is the 'inversion'. Traditional approach (wrong): Infrastructure layer defines interfaces, business layer uses them. Business depends on infrastructure. Inverted approach (correct): Business layer defines interfaces it needs, infrastructure implements them. Infrastructure depends on business. Package structure: /domain/services/OrderService.ts (uses IDatabase), /domain/interfaces/IDatabase.ts (defines interface), /infrastructure/MySQLDatabase.ts (implements IDatabase, imports interface from domain). Result: Domain layer has zero dependencies on infrastructure. Fully testable with mocks. Infrastructure depends on domain interfaces. Can swap entire infrastructure without touching domain. Interface ownership: Client owns interface - defines contract it needs. Provider implements client's contract. This enables: Hexagonal/Clean Architecture (domain at center, infrastructure at edges), plugin architecture (plugins implement domain interfaces), framework independence (domain unaware of Express, NestJS, etc.), testability (domain tested without infrastructure). Example: OrderService defines IPaymentGateway interface representing what it needs. Stripe/PayPal/Square plugins implement interface. Domain drives interface design, not infrastructure capabilities. DIP inverts control: High-level dictates contract, low-level conforms.

99% confidence
A

NestJS approach (built-in DI): Use @Injectable() decorator to mark providers. Define interfaces in domain layer. Example: interface IUserRepository {findById(id: string): Promise}. Implementation: @Injectable() class MongoUserRepository implements IUserRepository {findById(id) {/* MongoDB logic */}}. Register provider: @Module({providers: [{provide: 'IUserRepository', useClass: MongoUserRepository}]}) - bind interface to implementation. Inject: constructor(@Inject('IUserRepository') private repo: IUserRepository) - NestJS resolves automatically. Module-based organization: Domain module defines interfaces, Infrastructure module provides implementations. InversifyJS approach (lightweight container): Install: npm install inversify reflect-metadata. Enable decorators: experimentalDecorators, emitDecoratorMetadata in tsconfig.json. Define: @injectable() class MySQLDatabase implements IDatabase. Register: container.bind('IDatabase').to(MySQLDatabase). Inject: @inject('IDatabase') private db: IDatabase. Resolve: const service = container.resolve(OrderService). Benefits: SOLID-focused, framework-agnostic, explicit bindings. Modern pattern (2024): Use decorators for metadata, constructor injection for immutability, module/container for organization. Testing: Override bindings with mocks. Example: container.rebind('IDatabase').to(MockDatabase). Recommendation: NestJS for full framework, InversifyJS for Node.js apps needing DI without framework lock-in.

99% confidence
A

Clean Architecture applies DIP to enforce inward dependency flow. DIP is foundational principle, Clean Architecture is architectural application. DIP: High-level modules depend on abstractions, not implementations. Clean Architecture: Concentric circles with dependency rule - source code dependencies point only inward. Inner circles (entities, use cases) define interfaces. Outer circles (interface adapters, frameworks) implement them. Benefits: Entities remain pure business rules (no framework dependencies), Use cases only depend on entity interfaces, Frameworks are replaceable plugins. Without DIP: Clean Architecture impossible - layers would depend outward on frameworks. With DIP: Frameworks depend on application, not vice versa. Example: Use Case defines UserRepository interface, Infrastructure layer implements it. Use Case unaware of PostgreSQL vs MongoDB. DIP enables clean layering, framework independence, testability.

99% confidence
A

Hexagonal Architecture (Ports & Adapters) uses DIP to invert control flow. Domain defines ports (interfaces) representing what it needs. Infrastructure implements adapters that plug into ports. Pattern: Domain center (hexagon) has inbound ports (IUseCase) and outbound ports (IRepository). UI/Controllers implement inbound ports, Database/Email implement outbound ports. DIP inversion: Instead of domain calling infrastructure, infrastructure implements domain's required interfaces. Example: OrderProcessor defines IPaymentGateway port. StripePaymentAdapter implements IPaymentGateway. OrderProcessor calls port.process(), StripeAdapter provides implementation. Benefits: Domain remains pure (no infrastructure), adapters swappable (Stripe → PayPal), business logic testable without external services. Ports = contracts domain needs, Adapters = concrete implementations. DIP is what makes hexagonal architecture possible - domain dictates interfaces, infrastructure conforms.

99% confidence
A

Clean Architecture dependency flow always points inward. Outer layers depend on inner layers, never reverse. Layer order: Entities (innermost) → Use Cases → Interface Adapters → Frameworks/Drivers (outermost). Entities: Pure business rules, no dependencies. Use Cases: Depend only on Entities (application-specific business rules). Interface Adapters: Depend on Use Cases, convert data formats (controllers, presenters). Frameworks: Depend on all inner layers (web, database, external services). Flow: HTTP Request → Controller (Framework) → Presenter (Adapter) → Use Case → Entity. Dependencies flow opposite direction: Controller → Use Case → Entity (inward). Rule: Inner circle knows nothing about outer circles. Outer circles contain mechanisms (web UI, database, external APIs), inner circles contain policies (business rules). Example: Controller calls Use Case interface, Use Case calls Entity methods, Entity never calls Use Case or Controller. Inward flow enables testing: Mock outer layers, test inner layers in isolation.

99% confidence
A

In Hexagonal Architecture, interfaces belong to the domain (inside the hexagon), not infrastructure. Domain defines ports (interfaces) representing what domain needs from outside. Infrastructure implements adapters that conform to these ports. Pattern: Domain/UserManagement.ts defines IPersistencePort, INotificationPort. Infrastructure/PostgresAdapter implements IPersistencePort, EmailAdapter implements INotificationPort. Rule: Ports defined by domain, adapters implemented by infrastructure. Benefits: Domain independent of infrastructure, adapters swappable without changing domain, business logic testable with mock adapters. Example: OrderService defines IOrderRepository interface with save(), findById() methods. PostgreSQL implements OrderRepository adapter, MongoDB implements MongoOrderRepository adapter. Both adapters connect to same domain port. Domain code: constructor(private repo: IOrderRepository) - depends on interface defined in domain, not concrete implementation. Port location: Inside domain boundary, not shared with infrastructure. Interface ownership: Domain owns contracts, infrastructure provides implementations.

99% confidence
A

Constructor Injection passes dependencies via class constructor, providing required dependencies upfront. Pattern: class OrderService {constructor(private db: IDatabase, private logger: ILogger) {}}. All dependencies declared in constructor signature, immutable via readonly/private. Benefits: Compile-time safety (missing dependencies caught early), clear dependency graph (constructor shows all requirements), immutable state (can't change dependencies after creation), easier testing (mock in constructor). Use for: Required dependencies that class can't function without. Modern frameworks (NestJS, Angular, Spring) prefer constructor injection as default. TypeScript: constructor(private readonly db: IDatabase) ensures immutability. Inversion of Control container resolves constructor dependencies automatically. Example: const service = new OrderService(database, logger) - dependencies explicit, no hidden requirements. Avoid when: Dependencies are optional, need runtime resolution, or would cause circular dependencies (use Setter Injection instead).

99% confidence
A

Setter Injection provides dependencies through setter methods after object creation. Pattern: class OrderService {private db: IDatabase; setDatabase(db: IDatabase) {this.db = db;}}. Benefits: Optional dependencies with reasonable defaults, reconfigurable at runtime, resolves circular dependencies. Drawbacks: (1) Mutable state - dependencies can change during object lifetime, (2) Incomplete state - object may exist without required dependencies, (3) Runtime errors - missing dependencies discovered at usage time, not creation time, (4) Hidden dependencies - constructor doesn't reveal what class needs, (5) Thread safety issues - concurrent access during setter calls. Use for: Optional features that can be configured later, legacy code compatibility, resolving circular dependencies. Modern preference: Use Constructor Injection for required dependencies, Setter only for truly optional ones. Framework support: Spring supports setter injection via @Autowired on setters, but recommends constructor injection as primary choice.

99% confidence
A

Interface Injection (classic definition) provides dependencies through interfaces that define injector methods - NOT to be confused with injecting interfaces (modern best practice). Pattern: interface IDatabaseInjector {injectDatabase(db: IDatabase);} class OrderService implements IDatabaseInjector {injectDatabase(db: IDatabase) {this.db = db;}}. Container calls injectDatabase() to provide dependency. Drawbacks: (1) Invasive - classes must implement injection interfaces, (2) Framework-specific coupling, (3) Complex - requires multiple interfaces for multiple dependencies, (4) Largely obsolete in 2025. Historical use: Early Java EE (Avalon framework), legacy .NET containers. Modern consensus (2025): Constructor injection preferred across Spring 6+, NestJS, Angular 17+. Note: 'Injecting interfaces' (depending on abstractions) remains best practice - this Q&A refers specifically to the obsolete injector method pattern. TypeScript limitation: Interfaces disappear at runtime, requiring workarounds (string tokens, abstract classes). Use interface injection only when: Legacy framework requires it, migrating ancient codebases. Otherwise: Use constructor injection with interface dependencies (e.g., constructor(private db: IDatabase)) - this follows DIP without interface injection pattern.

99% confidence
A

Constructor Injection should be the default choice for 95% of cases. Reasons: (1) Compile-time safety - missing dependencies caught immediately, (2) Immutability - dependencies can't change after creation, (3) Clear requirements - constructor shows all needed dependencies, (4) Testability - easy to mock in tests, (5) No hidden state - object fully formed after construction. Constructor injection promotes good design: Required dependencies explicit, object always in valid state, easier to understand and maintain. Use Setter Injection only for: Optional dependencies with reasonable defaults, circular dependencies (rare in good design), legacy system integration. Avoid Interface Injection unless framework demands it. Modern framework consensus: Spring, NestJS, Angular all recommend constructor injection as primary choice. Example: OrderService requires Database and Logger - constructor injection. OrderService optionally uses Cache - setter injection with default NullCache. Rule: Start with constructor injection, use setter only when you have specific reason for optional/reconfigurable dependencies.

99% confidence
A

DIP in React means components depend on abstractions (context, props interfaces) not concrete implementations. Pattern: Context API for dependency injection. Violation: Component imports concrete service directly. class UserList extends Component {componentDidMount() {const api = new UserAPI(); api.fetchUsers().then(setUsers);}} - depends on concrete UserAPI. DIP compliant: Define abstraction via Context. interface IUserService {fetchUsers(): Promise<User[]>} const UserServiceContext = React.createContext(null). Provider injects implementation: <UserServiceContext.Provider value={new APIUserService()}></UserServiceContext.Provider>. Component depends on abstraction: const service = useContext(UserServiceContext); useEffect(() => {service.fetchUsers()}, []). Benefits: Testability (inject mock service), flexibility (swap API implementation), loose coupling. Modern 2025 patterns: (1) Custom hooks as abstractions: function useUsers(): User[] - implementation hidden, consumers depend on hook interface. (2) Render props: <DataProvider render={(data) => } /> - component depends on render prop signature. (3) Higher-order components: withUserService(UserList) - HOC injects dependency. Best practice: Use Context for cross-cutting concerns (auth, theme, i18n), props for direct dependencies, custom hooks for encapsulated logic.

99% confidence
A

Common React DIP violations: (1) Direct API imports in components. import {fetchUsers} from '../api/users'; useEffect(() => {fetchUsers()}, []) - component depends on concrete API module. Fix: Inject via Context or custom hook. (2) Hardcoded services. const [data, setData] = useState(); useEffect(() => {axios.get('/api/users').then(setData)}, []) - depends on axios, hardcoded URL. Fix: useDataService hook abstracting data fetching. (3) localStorage coupling. useEffect(() => {const token = localStorage.getItem('token')}, []) - depends on browser API. Fix: Storage abstraction interface IStorage {get(key), set(key, value)} with implementations LocalStorage, SessionStorage, MockStorage. (4) Router dependency. import {useNavigate} from 'react-router-dom'; navigate('/home') - couples to specific router library. Fix: Navigation abstraction or accept navigate as prop. (5) Global state imports. import {store} from '../store' - couples to concrete store. Fix: Context provider for state. Modern patterns: Custom hooks encapsulate dependencies (useAuth, useAPI, useStorage), making components depend on hook contracts, not implementations. Testing: Components using abstractions easily tested with mocks. const mockService = {fetchUsers: jest.fn()}; <ServiceContext.Provider value={mockService}>.

99% confidence
A

DIP enables testing by allowing mock injection instead of real dependencies. Without DIP: class OrderService {private db = new MySQLDatabase(); process(order) {this.db.save(order);}} - tests require real MySQL database, slow integration tests. With DIP: class OrderService {constructor(private db: IDatabase)} - inject mock in tests. Test setup: const mockDb: IDatabase = {save: jest.fn(), find: jest.fn()}; const service = new OrderService(mockDb); service.process(order); expect(mockDb.save).toHaveBeenCalledWith(order) - fast unit test, no database. Benefits: (1) Fast tests - no I/O, network, or database calls. (2) Isolated tests - test single class behavior without dependencies. (3) Deterministic - mocks return predictable values, no flaky tests. (4) Easy setup - create simple mocks, no complex fixtures. Testing pyramid: Unit tests (mock all dependencies via DI), Integration tests (test with real dependencies), E2E tests (full system). DIP enables unit test coverage at minimal cost. Modern tools: Jest/Vitest mock functions, TypeScript jest.Mocked for type-safe mocks, testing-library with Context providers for React. Pattern: Test doubles via DI - Mocks (verify behavior), Stubs (provide canned responses), Fakes (working implementations like in-memory database). All injected via abstractions defined by DIP.

99% confidence
A

DIP in microservices ensures services depend on abstract contracts (APIs, message schemas) not concrete implementations. Pattern: Service defines interface for external dependencies, adapters implement interface for different backends. Example: OrderService needs PaymentGateway. Define: interface IPaymentGateway {charge(amount): Promise}. OrderService depends on interface. Adapters: StripeAdapter implements IPaymentGateway (calls Stripe API), PayPalAdapter implements IPaymentGateway (calls PayPal API). Configuration determines which adapter to inject. Benefits: (1) Service testable with mock gateway. (2) Switch payment providers without OrderService changes. (3) Multi-gateway support (route by region/amount). Inter-service communication: Define message contracts (Protobuf, JSON Schema), services depend on contracts not implementations. Event-driven: OrderService publishes OrderCreated event to interface IEventBus, infrastructure implements with RabbitMQ, Kafka, or AWS SNS. Service mesh pattern: Services communicate via abstraction layer (Istio, Linkerd), not direct HTTP calls. Modern 2025: gRPC with Protobuf contracts, GraphQL Federation with schema contracts, OpenAPI specs as contracts. Best practice: API versioning for backward compatibility, contract testing (Pact) ensures implementations match contracts, consumer-driven contracts put interface ownership with consumers (DIP).

99% confidence
A

Circular dependencies violate good design - A depends on B, B depends on A. DIP helps resolve via abstraction layers. Problem: class UserService {constructor(private orders: OrderService)} class OrderService {constructor(private users: UserService)} - circular dependency, container fails to resolve. Solution 1 - Extract interface: interface IUserLookup {findUser(id)}. OrderService depends on IUserLookup, UserService implements IUserLookup. Break cycle: OrderService → IUserLookup ← UserService. Solution 2 - Lazy injection (Setter): OrderService uses constructor injection for primary deps, setter injection for circular: setUserService(users: UserService). Delay injection after both constructed. Solution 3 - Event-driven: Remove direct dependency. OrderService publishes OrderCreated event, UserService subscribes. No direct coupling. Solution 4 - Intermediate service: Extract shared logic to new service. OrderUserCoordinator depends on both UserService and OrderService. Neither depends on each other. Solution 5 - Redesign: Circular dependency often indicates wrong boundaries. Move shared concern to separate module both depend on. TypeScript: Use forwardRef() in NestJS, Provider in InversifyJS for lazy resolution. Best practice: Circular dependencies are code smell - indicates missing abstraction or wrong service boundaries. Prefer Solutions 3-4 (eliminate dependency) over Solution 2 (setter workaround).

99% confidence
A

Factory pattern inverts dependency by having high-level code depend on factory interface, low-level code implement concrete factories. Pattern: High-level defines what it needs via factory interface. interface IPaymentProcessorFactory {create(type: string): IPaymentProcessor}. High-level depends on factory: class OrderService {constructor(private factory: IPaymentProcessorFactory)} process(order: Order) {const processor = this.factory.create(order.paymentType); processor.charge(order.amount);}. Low-level implements factory: class ConcretePaymentFactory implements IPaymentProcessorFactory {create(type: string): IPaymentProcessor {switch(type) {case 'stripe': return new StripeProcessor(); case 'paypal': return new PayPalProcessor();}}}. DIP achieved: OrderService depends on abstractions (IPaymentProcessorFactory, IPaymentProcessor), not concrete classes (StripeProcessor, PayPalProcessor). Factory creates concrete classes but OrderService unaware. Benefits: Centralized creation logic, runtime type selection, testable (mock factory returns test doubles). Abstract Factory: Factory for families of related objects. Modern TypeScript: Use DI container instead of manual factory for complex scenarios. container.bind('stripe').to(StripeProcessor). Factory Method pattern: Subclass overrides factory method to change creation logic - also inverts dependency on concrete product classes.

99% confidence
A

DIP in monorepos ensures modules depend on shared abstractions, not concrete implementations from other modules. Architecture: Shared module defines interfaces, domain modules implement interfaces, application modules depend on shared interfaces only. Example structure: /packages/shared/interfaces/IUserRepository.ts - interface definitions. /packages/user-domain/UserService.ts - implements business logic, depends on IUserRepository. /packages/user-infra/MongoUserRepository.ts - implements IUserRepository using MongoDB. Application: import {IUserRepository} from '@shared/interfaces'; import {UserService} from '@user-domain' - depends only on interfaces and domain, not infrastructure. Benefits: (1) Clear module boundaries - domain unaware of infrastructure. (2) Independent development - teams work on different modules without coupling. (3) Testability - domain tested without infrastructure modules. (4) Build optimization - change in infra doesn't rebuild domain. Module dependency graph: Application → Domain → Shared Interfaces ← Infrastructure. Infrastructure depends inward, never outward. Modern tools: Nx, Turborepo enforce dependency constraints via lint rules. nx.json: {"projectDependencies": {"user-domain": ["shared"], "user-infra": ["shared", "user-domain"]}} - prevents domain from importing infra. Best practice: Use barrel exports from shared module, define dependency constraints in monorepo config, architectural tests verify dependency rules (ArchUnit-style).

99% confidence
A

DIP in serverless means Lambda functions depend on abstractions for AWS services, databases, and external APIs - enables local testing and provider independence. Violation: Lambda imports AWS SDK directly. import {DynamoDB} from 'aws-sdk'; export const handler = async (event) => {const db = new DynamoDB(); await db.putItem({TableName: 'Users', Item: event.body});}. Problem: Can't test locally, coupled to DynamoDB. DIP solution: Define interface for data access. interface IUserRepository {save(user: User): Promise}. Lambda depends on abstraction. export const handler = async (event, context, repository: IUserRepository = new DynamoUserRepository()) => {await repository.save(event.body);}. Testing: Inject mock repository. const mockRepo: IUserRepository = {save: jest.fn()}; await handler(event, context, mockRepo). Benefits: Local development with in-memory implementation, unit testing without AWS, multi-cloud support (swap DynamoDB for CosmosDB). Dependency injection in Lambda: (1) Constructor injection via wrapper: const repository = new DynamoUserRepository(); exports.handler = (event) => lambdaLogic(event, repository). (2) Singleton pattern: Lazy initialization outside handler, reused across invocations. let repo: IUserRepository; exports.handler = async (event) => {repo = repo || createRepository(); await repo.save()}. Modern 2025: Middy middleware for DI, AWS Lambda Powertools for observability abstraction, SST (Serverless Stack) for infrastructure abstraction. Best practice: Separate Lambda handler (thin adapter) from business logic (depends on interfaces), test business logic without Lambda runtime.

99% confidence
A

DI containers add minimal overhead but require configuration for optimal performance. Performance factors: (1) Resolution cost - container uses reflection/metadata to resolve dependencies. TSyringe/InversifyJS: 1M-6M operations/sec (negligible for most apps). (2) Lifecycle management - Singleton (resolve once, cache), Transient (create every time, slower), Scoped (per request, middle ground). Recommendation: Use Singleton for stateless services (logging, config), Transient only when state isolation required. (3) Lazy vs eager initialization - Lazy: Resolve on first use (faster startup, slower first request). Eager: Resolve at app startup (slower startup, consistent latency). Production: Use eager for critical path dependencies. (4) Circular dependencies - Container recursion adds overhead. Avoid circular deps via design. Benchmarks (2025): InversifyJS container resolution: 1.6M ops/sec, TSyringe: 5M ops/sec, Manual DI (new): 50M ops/sec. Reality: Difference is 200ns vs 20ns - insignificant compared to I/O (ms). Optimization tips: (1) Prefer singleton scope for services, (2) Use manual DI for hot paths if profiling shows issue, (3) Avoid excessive decorator metadata, (4) Lazy-load heavy dependencies. Bottom line: DI container overhead is negligible. Focus on code clarity and testability, not micro-optimization. Only optimize if profiling proves container is bottleneck (rare).

99% confidence
A

Refactor legacy code to DIP incrementally via Strangler Fig pattern - gradually replace concrete dependencies with abstractions. Step 1 - Identify concrete dependencies: Find new instantiations of external services (new MySQLDatabase(), new EmailService(), import axios). Step 2 - Extract interfaces: Create interface matching existing concrete class. interface IDatabase extends MySQLDatabase {} - start with structural match. Step 3 - Constructor injection: Change class to accept interface. Before: class OrderService {private db = new MySQLDatabase()}. After: class OrderService {constructor(private db: IDatabase = new MySQLDatabase())} - default maintains backward compatibility. Step 4 - Introduce factory/builder: Centralize object creation. class ServiceFactory {static createOrderService(): OrderService {return new OrderService(new MySQLDatabase());}}. Callers use factory, enabling future DI container. Step 5 - Simplify interface: Remove MySQL-specific methods from IDatabase, make truly abstract. Step 6 - Add DI container gradually: Start with new features using container, migrate existing code over time. Testing strategy: Write characterization tests before refactoring, test existing behavior preserved. Add new tests for injected mocks. Sprout pattern: New functionality uses DI from start, old code incrementally refactored. Anti-pattern: Big bang rewrite - refactor gradually. Legacy adapters: Wrap old concrete classes with adapter implementing new interfaces. Modern tools: Approval tests for legacy behavior, code coverage to find untested areas (high risk for refactoring).

99% confidence