DEPENDENCY INJECTION PROJECTS
Dependency Injection: Learning Through Building
“Dependency Injection is a 25-dollar term for a 5-cent concept.” - James Shore
This document contains projects designed to make you deeply understand Dependency Injection—not just use a framework, but truly grasp why it exists and how it works under the hood.
The Core Problem DI Solves
Before diving into projects, understand the fundamental issue:
# WITHOUT DI - Tightly Coupled
class OrderService:
def __init__(self):
self.database = MySQLDatabase() # Hardcoded!
self.emailer = SMTPEmailer() # Hardcoded!
self.logger = FileLogger() # Hardcoded!
def place_order(self, order):
self.database.save(order)
self.emailer.send(order.customer_email, "Order placed!")
self.logger.log(f"Order {order.id} placed")
Problems:
- Can’t test without a real MySQL database
- Can’t switch to PostgreSQL without editing this file
- Can’t disable emails during testing
- Hidden dependencies - you don’t know what this class needs until you read all the code
# WITH DI - Loosely Coupled
class OrderService:
def __init__(self, database, emailer, logger): # Injected!
self.database = database
self.emailer = emailer
self.logger = logger
def place_order(self, order):
self.database.save(order)
self.emailer.send(order.customer_email, "Order placed!")
self.logger.log(f"Order {order.id} placed")
Now you can:
- Test with a fake database (in-memory)
- Swap email providers without touching this code
- See exactly what the class needs just by looking at the constructor
Project 1: The “Before and After” Refactoring Challenge
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Python
- Alternative Programming Languages: TypeScript, Java, C#
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 1: Beginner
- Knowledge Area: Software Design / Refactoring
- Software or Tool: None (pure code)
- Main Book: “Clean Code” by Robert C. Martin
What you’ll build: Take a 500-line tightly-coupled application (e-commerce order processor) and refactor it to use DI, adding tests that were previously impossible.
Why it teaches DI: You’ll feel the pain first. You’ll try to write tests and realize you CAN’T without a real database. You’ll want to swap email providers and realize you have to edit 15 files. Then you’ll fix it.
Core challenges you’ll face:
- Identifying hidden dependencies → maps to understanding coupling
- Extracting interfaces → maps to abstraction design
- Creating a composition root → maps to wiring dependencies
- Writing tests with mocks → maps to seeing the payoff
Key Concepts:
- Tight vs Loose Coupling: “Clean Code” Chapter 11 - Robert C. Martin
- Interface Segregation: “Clean Architecture” Chapter 10 - Robert C. Martin
- Dependency Inversion Principle: “SOLID Principles of Object-Oriented and Agile Design” - Robert C. Martin
Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic OOP in any language
Real world outcome:
- Start with code that has 0% test coverage because tests are impossible
- End with code that has 90%+ test coverage
- Run tests that complete in milliseconds (no real DB, no real email)
- Swap the database from SQLite to PostgreSQL by changing ONE line
Learning milestones:
- Feel the pain → You understand why tightly-coupled code is a nightmare
- Extract interfaces → You understand what abstractions are for
- Tests pass without external services → You’ve internalized the value of DI
Project 2: Build Your Own DI Container from Scratch
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Python
- Alternative Programming Languages: TypeScript, C#, Java
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Metaprogramming / Reflection
- Software or Tool: Building a DI Framework
- Main Book: “Dependency Injection Principles, Practices, and Patterns” by Steven van Deursen & Mark Seemann
What you’ll build: A fully functional DI container that can register types, resolve dependencies automatically, handle lifetimes (singleton, transient, scoped), and detect circular dependencies.
Why it teaches DI: Building the machinery reveals how the magic works. You’ll understand why containers exist, what problems they solve, and when you don’t need one.
Core challenges you’ll face:
- Automatic dependency resolution (reading constructor signatures) → maps to reflection/introspection
- Lifetime management (when to create new vs reuse) → maps to object lifecycle
- Circular dependency detection (A needs B, B needs A) → maps to graph algorithms
- Lazy resolution (don’t create until needed) → maps to proxy patterns
- Scope management (per-request in web apps) → maps to context handling
Resources for key challenges:
- “Dependency Injection Principles, Practices, and Patterns” by Steven van Deursen & Mark Seemann - The definitive book on DI containers
Key Concepts:
- Reflection/Introspection: “Fluent Python” Chapter 22 - Luciano Ramalho
- Object Lifecycle Management: “Dependency Injection Principles” Chapter 8 - Steven van Deursen
- Service Locator Anti-pattern: “Clean Architecture” Chapter 22 - Robert C. Martin
- Graph Cycle Detection: “Grokking Algorithms” Chapter 6 - Aditya Bhargava
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Understanding of reflection, basic graph theory
Real world outcome:
# Your container in action
container = Container()
container.register(IDatabase, MySQLDatabase, lifetime=Lifetime.SINGLETON)
container.register(IEmailer, SMTPEmailer)
container.register(OrderService)
# Automatically resolves all dependencies!
order_service = container.resolve(OrderService)
- Run
python demo.pyand see your container automatically wire up a 10-class dependency graph - Add a circular dependency and see your container detect and report it
- Create scoped lifetimes that work correctly in a multi-threaded environment
Learning milestones:
- Basic registration and resolution works → You understand the core pattern
- Lifetimes work correctly → You understand singleton vs transient vs scoped
- Circular dependencies are detected → You understand the graph nature of dependencies
- You can explain when NOT to use a container → You’ve truly mastered DI
Project 3: Plugin Architecture System
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Python
- Alternative Programming Languages: TypeScript, Go, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 2: Intermediate
- Knowledge Area: Software Architecture / Extensibility
- Software or Tool: Plugin System (like VSCode extensions)
- Main Book: “Clean Architecture” by Robert C. Martin
What you’ll build: A text editor (or CLI tool) with a plugin system. Plugins are loaded at runtime and can add commands, modify behavior, and extend functionality—all without modifying core code.
Why it teaches DI: Plugins ARE dependency injection at runtime. The core application doesn’t know what plugins exist—they’re injected. This is DI’s most powerful use case.
Core challenges you’ll face:
- Defining plugin interfaces → maps to contract design
- Dynamic loading (load code at runtime) → maps to late binding
- Plugin isolation (plugins shouldn’t break each other) → maps to defensive design
- Hook system (plugins respond to events) → maps to observer pattern
- Plugin discovery (find plugins in a folder) → maps to convention over configuration
Key Concepts:
- Open/Closed Principle: “Clean Architecture” Chapter 8 - Robert C. Martin
- Dynamic Loading: “Fluent Python” Chapter 24 - Luciano Ramalho
- Plugin Patterns: “Pattern-Oriented Software Architecture, Volume 1” Chapter 5 - Frank Buschmann
- Event-Driven Architecture: “Building Microservices” Chapter 4 - Sam Newman
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Basic OOP, file system operations
Real world outcome:
./my-editor --plugins-dir ./plugins/
[Plugin Loaded] syntax-highlighter v1.0
[Plugin Loaded] git-integration v2.3
[Plugin Loaded] auto-formatter v1.5
> :format # Command added by auto-formatter plugin!
Formatted 150 lines.
- Drop a new
.pyfile into/plugins/and see it load automatically - Write a plugin that adds a new command in under 20 lines of code
- See core application work perfectly even if a plugin crashes
Learning milestones:
- Core app runs without any plugins → You understand the host/plugin boundary
- Plugins can add commands → You understand interface-based extension
- Plugins can respond to events → You understand loosely-coupled communication
- New plugins load without recompiling core → You’ve achieved true extensibility
Project 4: Multi-Database Repository Pattern
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Go
- Alternative Programming Languages: TypeScript, C#, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: Database / Persistence Patterns
- Software or Tool: Database Abstraction Layer
- Main Book: “Patterns of Enterprise Application Architecture” by Martin Fowler
What you’ll build: A user management system where you can switch between SQLite, PostgreSQL, and an in-memory store by changing configuration—zero code changes in business logic.
Why it teaches DI: This is the classic DI use case. Your business logic asks for “a database” without caring which one. Testing uses in-memory, development uses SQLite, production uses PostgreSQL.
Core challenges you’ll face:
- Designing the repository interface → maps to abstraction over implementation
- Handling database-specific features → maps to leaky abstractions
- Transaction management → maps to cross-cutting concerns
- Connection pooling differences → maps to implementation details hiding
- Migration compatibility → maps to real-world constraints
Key Concepts:
- Repository Pattern: “Patterns of Enterprise Application Architecture” Chapter 10 - Martin Fowler
- Unit of Work: “Patterns of Enterprise Application Architecture” Chapter 11 - Martin Fowler
- Interface Design: “Effective Go” - The Go Authors
- Database Abstractions: “Designing Data-Intensive Applications” Chapter 2 - Martin Kleppmann
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Basic SQL, one database library experience
Real world outcome:
# Development
DATABASE=sqlite ./app serve
# Listening on :8080, using SQLite
# Production
DATABASE=postgres CONNECTION_STRING=... ./app serve
# Listening on :8080, using PostgreSQL
# Tests
go test ./...
# All 200 tests pass in 0.3s (using in-memory store)
- See your test suite run in milliseconds with in-memory storage
- Switch databases with a single environment variable
- Add a new database (MySQL) by implementing ONE interface
Learning milestones:
- Tests run without a real database → You understand mock/fake injection
- Switching databases requires no code changes → You understand abstraction power
- You encounter a “leaky abstraction” → You understand DI limitations
- You add a new database implementation → You’ve proven the design works
Project 5: Notification Service with Strategy Pattern
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Python, Java, C#
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 1: Beginner
- Knowledge Area: Design Patterns / Messaging
- Software or Tool: Notification System
- Main Book: “Head First Design Patterns” by Eric Freeman
What you’ll build: A notification system that can send via Email, SMS, Push Notification, or Slack—with the channel selected at runtime based on user preferences.
Why it teaches DI: Each notification channel is a strategy injected at runtime. The system doesn’t hardcode “send email”—it calls “notifier.send()” and the injected implementation decides how.
Core challenges you’ll face:
- Common interface for different channels → maps to strategy pattern
- Handling channel-specific data (SMS has phone, email has address) → maps to interface design trade-offs
- Fallback logic (if SMS fails, try email) → maps to decorator/chain patterns
- User preference routing → maps to factory pattern
- Testing without real external services → maps to mocking external APIs
Key Concepts:
- Strategy Pattern: “Head First Design Patterns” Chapter 1 - Eric Freeman
- Factory Pattern: “Head First Design Patterns” Chapter 4 - Eric Freeman
- Interface Segregation: “Clean Architecture” Chapter 10 - Robert C. Martin
- External Service Mocking: “Working Effectively with Legacy Code” Chapter 8 - Michael Feathers
Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic OOP, any HTTP client experience
Real world outcome:
// User preferences determine which notifier is injected
const notifier = notifierFactory.createFor(user);
await notifier.send("Your order has shipped!");
// Console output:
// [Email] Sent to: user@example.com - "Your order has shipped!"
// or
// [SMS] Sent to: +1234567890 - "Your order has shipped!"
- Run tests that verify notification logic WITHOUT sending real emails
- Add a new channel (Discord) by implementing ONE interface
- See user preferences correctly route to different channels
Learning milestones:
- All channels work through same interface → You understand polymorphism
- Tests pass without external services → You understand mock injection
- Adding new channel is trivial → You understand Open/Closed Principle
- Fallback chain works → You understand decorator composition
Project 6: Build a Web Framework with Middleware
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Go
- Alternative Programming Languages: TypeScript, Rust, Python
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: Web / HTTP / Middleware
- Software or Tool: Web Framework (like Express/Gin)
- Main Book: “The Go Programming Language” by Alan Donovan & Brian Kernighan
What you’ll build: A minimal web framework where middleware (logging, auth, rate limiting) is injected into the request pipeline. Routes can declare what middleware they need.
Why it teaches DI: Middleware IS dependency injection. Each request handler receives injected services (logger, auth, database) through middleware. The handler doesn’t create these—they’re provided.
Core challenges you’ll face:
- Middleware chaining (each calls next) → maps to chain of responsibility
- Request-scoped services (new DB transaction per request) → maps to scoped lifetime
- Middleware ordering (auth before business logic) → maps to dependency ordering
- Context propagation (passing data through middleware) → maps to context pattern
- Middleware composition (combine logging + auth + rate-limit) → maps to composition over inheritance
Key Concepts:
- Middleware Pattern: “Learning Go” Chapter 11 - Jon Bodner
- Chain of Responsibility: “Head First Design Patterns” Chapter 14 - Eric Freeman
- Request Context: “The Go Programming Language” Chapter 9 - Alan Donovan
- Scoped Services: “Dependency Injection Principles” Chapter 8 - Steven van Deursen
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: HTTP basics, one web framework experience
Real world outcome:
app := framework.New()
app.Use(middleware.Logger()) // Injected into all routes
app.Use(middleware.RateLimiter(100)) // 100 req/sec
app.Get("/api/users",
middleware.RequireAuth(), // Injected into this route
handlers.ListUsers,
)
app.Listen(":8080")
curl http://localhost:8080/api/users
# [LOG] GET /api/users 200 12ms
# [AUTH] User authenticated: admin
# {"users": [...]}
- See middleware execute in correct order
- Test handlers in isolation by injecting mock middleware
- Add custom middleware in under 10 lines of code
Learning milestones:
- Basic middleware chain works → You understand function composition
- Request-scoped services work correctly → You understand scoped lifetimes
- Routes can specify middleware → You understand declarative injection
- Tests run without HTTP → You understand the power of injected abstractions
Project 7: Configuration Management System
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Python
- Alternative Programming Languages: Go, TypeScript, Rust
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 1: Beginner
- Knowledge Area: DevOps / Configuration
- Software or Tool: Config Management (like dotenv)
- Main Book: “The Pragmatic Programmer” by David Thomas & Andrew Hunt
What you’ll build: A configuration system that loads from multiple sources (env vars, files, command line, remote config server) and injects configuration into services.
Why it teaches DI: Configuration is one of the most common things to inject. Your services shouldn’t know WHERE configuration comes from—they just receive it. This project makes that concrete.
Core challenges you’ll face:
- Multiple config sources (env, file, remote) → maps to adapter pattern
- Priority/override rules (env overrides file) → maps to composition
- Type-safe configuration (ensure port is a number) → maps to validation injection
- Hot reloading (config changes without restart) → maps to observer pattern
- Secret handling (don’t log passwords) → maps to security-aware design
Key Concepts:
- 12-Factor App Config: “The Pragmatic Programmer” Chapter 5 - David Thomas
- Adapter Pattern: “Head First Design Patterns” Chapter 7 - Eric Freeman
- Configuration Anti-patterns: “Clean Architecture” Chapter 21 - Robert C. Martin
- Environment-Based Config: “DevOps for the Desperate” Chapter 4 - Bradley Smith
Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic file I/O, environment variables
Real world outcome:
config = Config.load([
EnvSource(), # Highest priority
FileSource(".env"),
DefaultsSource({"port": 8080})
])
server = WebServer(
port=config.get_int("PORT"),
database_url=config.get_secret("DATABASE_URL"),
)
PORT=3000 python app.py
# [CONFIG] PORT: 3000 (from environment)
# [CONFIG] DATABASE_URL: *** (from .env file)
# Server listening on port 3000
- See clear logging of where each config value came from
- Override file config with environment variables
- Run tests with injected test configuration
Learning milestones:
- Config loads from multiple sources → You understand adapters
- Priority system works → You understand composition ordering
- Tests use isolated config → You understand injection benefits
- Hot reload works → You understand reactive injection
Project 8: Logging Framework with Multiple Backends
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Rust
- Alternative Programming Languages: Go, C, TypeScript
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: Observability / Logging
- Software or Tool: Logging Library (like log4j, slog)
- Main Book: “The Rust Programming Language” by Steve Klabnik
What you’ll build: A logging framework where logs can go to multiple destinations (file, stdout, remote server, database) simultaneously, with each destination injectable and configurable.
Why it teaches DI: Loggers are a perfect DI example—you want to inject different backends for different environments (stdout for dev, file + remote for prod, nothing for tests).
Core challenges you’ll face:
- Multiple simultaneous backends → maps to composite pattern
- Log levels per backend (verbose to file, errors only to remote) → maps to decorator pattern
- Structured logging (JSON vs plain text) → maps to formatter injection
- Async writing (don’t block on log writes) → maps to async patterns with DI
- Testing log output → maps to capturing injected output
Key Concepts:
- Composite Pattern: “Head First Design Patterns” Chapter 9 - Eric Freeman
- Decorator Pattern: “Head First Design Patterns” Chapter 3 - Eric Freeman
- Structured Logging: “The Practice of Network Security Monitoring” Chapter 3 - Richard Bejtlich
- Rust Traits for DI: “The Rust Programming Language” Chapter 10 - Steve Klabnik
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Basic Rust or language with interfaces/traits
Real world outcome:
let logger = LoggerBuilder::new()
.add(ConsoleBackend::new().with_level(Level::Debug))
.add(FileBackend::new("app.log").with_level(Level::Info))
.add(RemoteBackend::new("https://logs.example.com").with_level(Level::Error))
.build();
logger.info("Server started on port 8080");
// -> Console: [INFO] Server started on port 8080
// -> File: [2024-01-15 10:30:00] [INFO] Server started on port 8080
// (not sent to remote - level too low)
logger.error("Database connection failed");
// -> Console, File, AND Remote all receive this
- See logs appear in multiple destinations simultaneously
- Configure different log levels per destination
- Test log output by injecting a capturing backend
Learning milestones:
- Multiple backends receive logs → You understand composite injection
- Log levels filter correctly → You understand decorator injection
- Tests capture log output → You understand the testing power of DI
- Async writing works → You understand complex injection scenarios
Project 9: Payment Gateway Abstraction
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Python, Go, C#
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: E-commerce / Payments / API Design
- Software or Tool: Payment Integration
- Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann
What you’ll build: A payment system that abstracts over Stripe, PayPal, and a mock provider—with the ability to switch providers per transaction or per customer.
Why it teaches DI: Payment processing is a real-world case where DI is mandatory. You need mocks for testing, you need to support multiple providers, and you might route different customers to different providers.
Core challenges you’ll face:
- Unified payment interface (charge, refund, subscribe) → maps to interface design
- Provider-specific features (Stripe has more features than PayPal) → maps to interface segregation
- Idempotency (don’t charge twice) → maps to cross-cutting concerns
- Testing payments (use mock provider) → maps to test doubles
- Fallback/retry logic (if Stripe fails, try PayPal) → maps to resilience patterns
Key Concepts:
- Adapter Pattern: “Head First Design Patterns” Chapter 7 - Eric Freeman
- Idempotency: “Designing Data-Intensive Applications” Chapter 11 - Martin Kleppmann
- Anti-Corruption Layer: “Domain-Driven Design” Chapter 14 - Eric Evans
- Resilience Patterns: “Release It!” Chapter 5 - Michael T. Nygard
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: REST API experience, basic payment concepts
Real world outcome:
const paymentService = new PaymentService(
process.env.NODE_ENV === 'test'
? new MockPaymentProvider()
: new StripeProvider(process.env.STRIPE_KEY)
);
const result = await paymentService.charge({
amount: 9999,
currency: 'USD',
customer: customer.id,
});
// Works identically whether using Stripe, PayPal, or Mock
- Run your entire test suite without hitting any external APIs
- Switch payment providers with a configuration change
- Add a new provider by implementing ONE interface
Learning milestones:
- Mock provider allows testing → You understand why DI enables testing
- Switching providers requires no code changes → You understand abstraction value
- Provider-specific features work → You understand interface design trade-offs
- Fallback logic works → You understand advanced DI composition
Project 10: Command-Line Framework with Pluggable Commands
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Rust
- Alternative Programming Languages: Go, Python, TypeScript
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: CLI / Command Design Pattern
- Software or Tool: CLI Framework (like Click, Cobra)
- Main Book: “Effective Rust” by David Drysdale
What you’ll build: A CLI framework where commands are registered through DI, with automatic help generation, argument parsing injection, and the ability to add commands at runtime.
Why it teaches DI: CLI frameworks are a perfect DI demonstration—each command is an injected handler. The framework doesn’t hardcode commands; they’re registered and discovered.
Core challenges you’ll face:
- Command registration → maps to service registration
- Argument parsing → maps to parameter injection
- Help generation (introspect registered commands) → maps to reflection
- Sub-commands (git clone, git commit) → maps to hierarchical injection
- Testing commands → maps to isolated unit testing
Key Concepts:
- Command Pattern: “Head First Design Patterns” Chapter 6 - Eric Freeman
- Builder Pattern: “Effective Rust” Item 7 - David Drysdale
- Convention over Configuration: “The Pragmatic Programmer” Chapter 8 - David Thomas
- CLI Design: “Command-Line Rust” Chapter 2 - Ken Youens-Clark
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Basic CLI experience, argument parsing
Real world outcome:
let mut app = App::new("myctl");
app.register(InitCommand::new());
app.register(BuildCommand::new());
app.register(DeployCommand::new());
// Auto-generated help!
// $ myctl --help
// Commands:
// init Initialize a new project
// build Build the project
// deploy Deploy to production
app.run(std::env::args());
- See help auto-generated from registered commands
- Add new commands without modifying framework code
- Test commands in isolation
Learning milestones:
- Commands register and execute → You understand service registration
- Help auto-generates → You understand reflection/introspection
- Commands can be tested in isolation → You understand DI’s testing benefit
- Sub-commands work → You understand hierarchical composition
Project 11: Game Engine with Swappable AI Strategies
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Python
- Alternative Programming Languages: C++, Rust, TypeScript
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Game Development / AI
- Software or Tool: Game AI System
- Main Book: “Game Programming Patterns” by Robert Nystrom
What you’ll build: A turn-based game (chess, tic-tac-toe, or similar) where AI opponents are injected strategies. Swap between random AI, minimax, neural network, or even human input through the same interface.
Why it teaches DI: Game AI is a perfect strategy pattern demonstration. The game doesn’t care HOW the opponent decides moves—it just calls get_move() on the injected strategy.
Core challenges you’ll face:
- Unified player interface (human and AI both implement it) → maps to strategy pattern
- Different AI difficulties → maps to strategy selection
- AI vs AI mode → maps to composition
- Swapping AI mid-game → maps to runtime injection
- Testing with deterministic AI → maps to controlled testing
Key Concepts:
- Strategy Pattern: “Game Programming Patterns” Chapter 1 - Robert Nystrom
- Component Pattern: “Game Programming Patterns” Chapter 14 - Robert Nystrom
- AI Strategies: “Algorithms, Fourth Edition” Chapter 6 - Robert Sedgewick
- State Machines in Games: “Game Programming Patterns” Chapter 7 - Robert Nystrom
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Basic game logic, recursion (for minimax)
Real world outcome:
# Human vs Random AI
game = TicTacToe(
player_x=HumanPlayer(),
player_o=RandomAI()
)
# Minimax vs Neural Network
game = TicTacToe(
player_x=MinimaxAI(depth=5),
player_o=NeuralNetworkAI("model.pt")
)
# Same game, completely different behaviors!
game.play()
- Watch AI vs AI games complete in milliseconds
- Swap AI strategies at runtime
- Test game logic with deterministic (always-first-move) AI
Learning milestones:
- Human and AI work through same interface → You understand polymorphism
- AI can be swapped without game changes → You understand strategy injection
- Tests are deterministic → You understand controlled injection
- Hot-swapping works → You understand runtime injection
Project 12: Mocking/Faking Framework
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Python
- Alternative Programming Languages: TypeScript, Java, C#
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: Testing / Metaprogramming
- Software or Tool: Mocking Library (like Mockito, unittest.mock)
- Main Book: “Working Effectively with Legacy Code” by Michael Feathers
What you’ll build: A mocking framework that can create fake implementations of interfaces, record method calls, define return values, and verify interactions.
Why it teaches DI: Mocks exist BECAUSE of DI. Without DI, you can’t inject mocks. Building a mock framework forces you to understand exactly what makes code testable.
Core challenges you’ll face:
- Dynamic interface implementation → maps to metaprogramming/proxies
- Call recording → maps to interceptor pattern
- Return value configuration → maps to builder pattern
- Verification (was this method called?) → maps to assertion design
- Spy pattern (partial mocking) → maps to advanced DI patterns
Key Concepts:
- Test Doubles: “Working Effectively with Legacy Code” Chapter 23 - Michael Feathers
- Proxy Pattern: “Head First Design Patterns” Chapter 11 - Eric Freeman
- Metaprogramming: “Fluent Python” Chapter 23 - Luciano Ramalho
- Test Design: “Test Driven Development” Part II - Kent Beck
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Advanced OOP, reflection/metaprogramming
Real world outcome:
# Your mock framework in action
mock_db = Mock(Database)
mock_db.when("get_user", id=123).then_return(User(id=123, name="Alice"))
service = UserService(database=mock_db) # Inject mock
result = service.get_user_name(123)
assert result == "Alice"
mock_db.verify("get_user").called_with(id=123)
- Create mocks for any interface automatically
- Verify method calls with flexible matchers
- Test code that was previously “untestable”
Learning milestones:
- Basic mocks work → You understand interface substitution
- Call verification works → You understand test assertions
- Complex matchers work → You understand advanced testing patterns
- You can explain mock vs stub vs spy → You’ve mastered test doubles
Project 13: Service Mesh Sidecar Proxy
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Go
- Alternative Programming Languages: Rust, C++
- Coolness Level: Level 5: Pure Magic
- Business Potential: 5. The “Industry Disruptor”
- Difficulty: Level 4: Expert
- Knowledge Area: Distributed Systems / Networking
- Software or Tool: Service Mesh (like Envoy/Istio)
- Main Book: “Building Microservices” by Sam Newman
What you’ll build: A sidecar proxy that intercepts service-to-service communication and injects cross-cutting concerns (auth, logging, retry, circuit breaker) transparently.
Why it teaches DI: A service mesh is DI at the infrastructure level. Services don’t implement retry logic—it’s injected by the sidecar. This is DI applied to distributed systems.
Core challenges you’ll face:
- Transparent proxying (intercept without code changes) → maps to proxy pattern
- Pluggable policies (retry, timeout, circuit breaker) → maps to chain of responsibility
- Service discovery (find where to route) → maps to registry pattern
- Observability injection (metrics, tracing) → maps to decorator pattern
- Configuration hot reload → maps to dynamic injection
Key Concepts:
- Sidecar Pattern: “Building Microservices” Chapter 12 - Sam Newman
- Circuit Breaker: “Release It!” Chapter 4 - Michael T. Nygard
- Service Discovery: “Building Microservices” Chapter 8 - Sam Newman
- Proxy Patterns: “Pattern-Oriented Software Architecture, Volume 2” Chapter 3 - Douglas Schmidt
Difficulty: Expert Time estimate: 1 month+ Prerequisites: TCP/HTTP deep knowledge, concurrency
Real world outcome:
# Start sidecar proxy
./sidecar --config policies.yaml &
# Service calls go through sidecar
curl localhost:8080/api/users
# [PROXY] Outbound request to user-service
# [PROXY] Retry 1/3 - 503 response
# [PROXY] Retry 2/3 - 200 OK
# [PROXY] Response time: 250ms (logged to metrics)
- See automatic retries without any code changes
- Watch circuit breaker open after failures
- Observe distributed tracing injected automatically
Learning milestones:
- Basic proxying works → You understand transparent interception
- Retry policies apply → You understand policy injection
- Circuit breaker works → You understand stateful proxies
- You can explain how Istio works → You understand enterprise DI
Project 14: Object-Relational Mapper (ORM) with Pluggable Drivers
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Python
- Alternative Programming Languages: TypeScript, Go, Rust
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: Database / Metaprogramming
- Software or Tool: ORM (like SQLAlchemy, TypeORM)
- Main Book: “Patterns of Enterprise Application Architecture” by Martin Fowler
What you’ll build: A mini-ORM that maps objects to database tables, with pluggable database drivers (SQLite, PostgreSQL, MySQL) injected at configuration time.
Why it teaches DI: ORMs are DI showcases. The ORM injects SQL dialects, connection pools, type converters—all swappable. Building one shows how DI enables supporting multiple databases.
Core challenges you’ll face:
- SQL dialect abstraction (different databases, different SQL) → maps to adapter pattern
- Connection pooling → maps to resource management injection
- Type conversion (Python datetime to SQL timestamp) → maps to converter injection
- Query building → maps to builder pattern
- Transaction management → maps to scoped services
Key Concepts:
- Data Mapper Pattern: “Patterns of Enterprise Application Architecture” Chapter 10 - Martin Fowler
- Identity Map: “Patterns of Enterprise Application Architecture” Chapter 12 - Martin Fowler
- Active Record vs Data Mapper: “Clean Architecture” Chapter 23 - Robert C. Martin
- Metaprogramming for ORM: “Fluent Python” Chapter 23 - Luciano Ramalho
Difficulty: Advanced Time estimate: 2-4 weeks Prerequisites: SQL, metaprogramming basics
Real world outcome:
class User(Model):
id = Column(Integer, primary_key=True)
name = Column(String(100))
email = Column(String(255))
# Same code, different databases!
db = Database(driver=SQLiteDriver("./app.db"))
# or
db = Database(driver=PostgresDriver("postgres://..."))
users = db.query(User).filter(User.name == "Alice").all()
- See the same model work on SQLite and PostgreSQL
- Add a new database by implementing driver interface
- Test with in-memory SQLite, production with PostgreSQL
Learning milestones:
- Basic CRUD works → You understand data mapping
- Multiple databases work → You understand driver injection
- Queries generate correct SQL per dialect → You understand adapters
- You understand ORM trade-offs → You’ve seen DI’s real-world complexity
Project 15: Async Task Queue with Pluggable Backends
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Python
- Alternative Programming Languages: Go, TypeScript, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: Distributed Systems / Messaging
- Software or Tool: Task Queue (like Celery, Sidekiq)
- Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann
What you’ll build: A task queue system where the message broker (Redis, RabbitMQ, in-memory) is injected, and task executors can be distributed across workers.
Why it teaches DI: Task queues separate “what to do” from “how to distribute it.” The broker is injected—swap Redis for RabbitMQ without changing task code. This is DI for distributed systems.
Core challenges you’ll face:
- Broker abstraction (different message queues) → maps to adapter pattern
- Task serialization → maps to codec injection
- Retry with backoff → maps to policy injection
- Worker pool management → maps to lifecycle management
- Testing without real broker → maps to mock broker injection
Key Concepts:
- Message Queues: “Designing Data-Intensive Applications” Chapter 11 - Martin Kleppmann
- Worker Pool Pattern: “Pattern-Oriented Software Architecture, Volume 2” Chapter 4 - Douglas Schmidt
- Retry Patterns: “Release It!” Chapter 5 - Michael T. Nygard
- Task Serialization: “Building Microservices” Chapter 4 - Sam Newman
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Concurrency, message queue concepts
Real world outcome:
# Configure broker via injection
queue = TaskQueue(broker=RedisBroker("redis://localhost"))
# or
queue = TaskQueue(broker=InMemoryBroker()) # For testing!
@queue.task
def send_email(to: str, subject: str, body: str):
# Task logic here
pass
# Enqueue task
send_email.delay("user@example.com", "Welcome!", "Thanks for signing up!")
# Start worker
queue.run_worker(concurrency=4)
- See tasks execute across multiple workers
- Swap brokers without changing task code
- Test task logic with in-memory broker (no Redis needed)
Learning milestones:
- Tasks enqueue and execute → You understand task abstraction
- Multiple brokers work → You understand adapter injection
- Tests run without external services → You understand mock broker value
- Distributed workers work → You understand scalable DI
Project 16: HTTP Client with Middleware/Interceptors
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Go
- Alternative Programming Languages: TypeScript, Python, Rust
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: HTTP / API Integration
- Software or Tool: HTTP Client (like Axios, httpx)
- Main Book: “Learning Go” by Jon Bodner
What you’ll build: An HTTP client where middleware can be injected to add logging, authentication, retry logic, caching, and request/response transformation.
Why it teaches DI: HTTP clients with middleware are DI in action. You inject auth handlers, retry policies, and loggers. The core client doesn’t know about these—they’re composed around it.
Core challenges you’ll face:
- Middleware chaining (logging wraps auth wraps retry) → maps to decorator pattern
- Request/response transformation → maps to interceptor pattern
- Retry with backoff → maps to policy injection
- Caching (skip network if cached) → maps to caching decorator
- Testing (mock HTTP responses) → maps to transport injection
Key Concepts:
- Decorator Pattern: “Head First Design Patterns” Chapter 3 - Eric Freeman
- HTTP Client Design: “Learning Go” Chapter 12 - Jon Bodner
- Retry Patterns: “Release It!” Chapter 5 - Michael T. Nygard
- HTTP Caching: “TCP/IP Guide” Chapter 80 - Charles M. Kozierok
Difficulty: Intermediate Time estimate: 1 week Prerequisites: HTTP basics, one HTTP client experience
Real world outcome:
client := NewClient().
Use(middleware.Logger()).
Use(middleware.Retry(3, time.Second)).
Use(middleware.Auth(bearerToken)).
Use(middleware.Cache(10 * time.Minute))
resp, err := client.Get("https://api.example.com/users")
// [LOG] GET /users
// [CACHE] Miss
// [AUTH] Added Bearer token
// [LOG] 200 OK in 120ms
- See middleware execute in order
- Watch retry logic handle transient failures
- Test with injected mock transport (no network)
Learning milestones:
- Middleware chains work → You understand decorator composition
- Auth injection works → You understand cross-cutting concerns
- Tests work without network → You understand transport injection
- You can explain Axios interceptors → You understand real-world DI
Project 17: Dependency Graph Visualizer
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Python
- Alternative Programming Languages: TypeScript, Go, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Static Analysis / Visualization
- Software or Tool: Dependency Analyzer
- Main Book: “Working Effectively with Legacy Code” by Michael Feathers
What you’ll build: A tool that analyzes code to extract dependency relationships and generates a visual graph showing how components depend on each other.
Why it teaches DI: Understanding dependencies visually reveals whether your DI is working. You’ll see tight coupling as a tangled mess and proper DI as clean, layered graphs.
Core challenges you’ll face:
- Code parsing (extract imports, constructor params) → maps to static analysis
- Graph construction → maps to graph data structures
- Cycle detection (find circular dependencies) → maps to graph algorithms
- Layered visualization → maps to graph layout
- Multiple languages → maps to adapter pattern for parsers
Key Concepts:
- Static Analysis: “Working Effectively with Legacy Code” Chapter 21 - Michael Feathers
- Graph Algorithms: “Grokking Algorithms” Chapter 6 - Aditya Bhargava
- Cycle Detection: “Algorithms, Fourth Edition” Chapter 4 - Robert Sedgewick
- Dependency Analysis: “Clean Architecture” Chapter 14 - Robert C. Martin
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Basic parsing, graph concepts
Real world outcome:
./dep-viz analyze ./src --output graph.svg
Analyzing 50 files...
Found 12 circular dependencies!
Graph exported to graph.svg
- See your codebase’s dependencies as a visual graph
- Identify circular dependencies instantly
- Compare “before DI” and “after DI” dependency graphs
Learning milestones:
- Dependencies extracted correctly → You understand static analysis
- Circular dependencies detected → You understand graph cycles
- Visualization is useful → You understand dependency structure
- You can identify DI violations visually → You understand DI architecture
Project 18: Feature Flag System with Runtime Injection
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Python, Go, Java
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: DevOps / Feature Management
- Software or Tool: Feature Flags (like LaunchDarkly)
- Main Book: “Continuous Delivery” by Jez Humble & David Farley
What you’ll build: A feature flag system that injects different implementations based on flag state, user attributes, or rollout percentages—without code changes or deploys.
Why it teaches DI: Feature flags ARE runtime dependency injection. Instead of hardcoding which payment provider to use, you inject the provider based on a flag. This is DI for production experimentation.
Core challenges you’ll face:
- Flag evaluation (rules, user targeting) → maps to strategy selection
- Flag storage (local, remote, cached) → maps to adapter pattern
- A/B testing (percentage rollouts) → maps to probabilistic selection
- Type-safe flag values → maps to typed injection
- Hot reload (change flags without restart) → maps to dynamic injection
Key Concepts:
- Feature Flags: “Continuous Delivery” Chapter 10 - Jez Humble
- A/B Testing: “Data Science for Business” Chapter 7 - Foster Provost
- Runtime Configuration: “Release It!” Chapter 17 - Michael T. Nygard
- Strategy Pattern: “Head First Design Patterns” Chapter 1 - Eric Freeman
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Basic web development
Real world outcome:
const flags = new FeatureFlags({
storage: new RemoteStorage("https://flags.example.com"),
cache: new InMemoryCache(60_000),
});
// Inject payment provider based on flag
const paymentProvider = flags.isEnabled("new-payment-flow", user)
? new StripeV2Provider()
: new StripeLegacyProvider();
// 10% rollout of new checkout
if (flags.isEnabled("new-checkout", user, { rolloutPercent: 10 })) {
return new NewCheckoutFlow();
}
- Toggle features without deploying code
- A/B test with percentage rollouts
- Target features to specific users
Learning milestones:
- Basic flags work → You understand conditional injection
- Rollouts work → You understand probabilistic selection
- User targeting works → You understand contextual injection
- Hot reload works → You understand dynamic reconfiguration
Project Comparison Table
| Project | Difficulty | Time | Depth of Understanding | Fun Factor |
|---|---|---|---|---|
| 1. Before/After Refactoring | Beginner | Weekend | ⭐⭐⭐ | ⭐⭐ |
| 2. DI Container from Scratch | Advanced | 1-2 weeks | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 3. Plugin Architecture | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 4. Multi-Database Repository | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 5. Notification Strategies | Beginner | Weekend | ⭐⭐⭐ | ⭐⭐⭐ |
| 6. Web Framework Middleware | Advanced | 2-3 weeks | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 7. Configuration System | Beginner | Weekend | ⭐⭐⭐ | ⭐⭐ |
| 8. Logging Framework | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 9. Payment Gateway | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 10. CLI Framework | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 11. Game AI Strategies | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 12. Mocking Framework | Advanced | 2-3 weeks | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 13. Service Mesh Sidecar | Expert | 1 month+ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 14. ORM with Drivers | Advanced | 2-4 weeks | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 15. Async Task Queue | Advanced | 2-3 weeks | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 16. HTTP Client Middleware | Intermediate | 1 week | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 17. Dependency Visualizer | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 18. Feature Flag System | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Recommendation Based on Context
If you’re a beginner:
Start with Project 1: Before/After Refactoring → This makes you FEEL the pain of tight coupling before showing you the solution. Nothing teaches like suffering first.
If you want deep understanding:
Build Project 2: DI Container from Scratch → Building the machinery demystifies everything. You’ll never be confused by Spring or Dagger again.
If you want something fun:
Build Project 11: Game AI Strategies → Watching different AI strategies battle each other is satisfying, and it’s a perfect strategy pattern demonstration.
If you’re focused on career:
Build Project 6: Web Framework with Middleware → This is what you’ll see in every job. Understanding middleware means understanding Express, Gin, ASP.NET Core, and more.
Final Capstone Project: Microservices Platform with Full DI Stack
- File: DEPENDENCY_INJECTION_PROJECTS.md
- Main Programming Language: Go
- Alternative Programming Languages: TypeScript, Rust
- Coolness Level: Level 5: Pure Magic
- Business Potential: 5. The “Industry Disruptor”
- Difficulty: Level 5: Master
- Knowledge Area: Distributed Systems / Platform Engineering
- Software or Tool: Microservices Platform
- Main Book: “Building Microservices” by Sam Newman
What you’ll build: A complete microservices platform that ties together everything: a DI container for each service, a service mesh for cross-service DI, a configuration system, feature flags, and a task queue—all with pluggable, injectable components.
Why it teaches DI: This is DI at every level—in-process DI for service internals, infrastructure DI for cross-cutting concerns, and platform DI for service-to-service communication. If you build this, you understand DI completely.
Core challenges you’ll face:
- Per-service DI containers → maps to service composition
- Cross-service communication (injected via service mesh) → maps to infrastructure DI
- Distributed configuration (injected config system) → maps to externalized configuration
- Feature flag integration (runtime behavior injection) → maps to dynamic DI
- Observability (tracing, metrics injected everywhere) → maps to cross-cutting concerns
- Testing the distributed system (inject mocks at every level) → maps to end-to-end DI
Resources for key challenges:
- “Building Microservices, 2nd Edition” by Sam Newman - The comprehensive guide to microservices architecture
- “Designing Data-Intensive Applications” by Martin Kleppmann - Deep dive into distributed systems
- “Release It!” by Michael T. Nygard - Production resilience patterns
Key Concepts:
- Microservices Architecture: “Building Microservices” Chapters 1-3 - Sam Newman
- Service Mesh: “Building Microservices” Chapter 12 - Sam Newman
- Distributed Tracing: “Building Microservices” Chapter 10 - Sam Newman
- Circuit Breakers: “Release It!” Chapter 4 - Michael T. Nygard
- Configuration Management: “Continuous Delivery” Chapter 3 - Jez Humble
- Feature Flags at Scale: “Continuous Delivery” Chapter 10 - Jez Humble
Difficulty: Master Time estimate: 2-3 months Prerequisites: All previous projects, distributed systems experience
Real world outcome:
microservices-platform/
├── services/
│ ├── user-service/ # DI container, injected repos
│ ├── order-service/ # DI container, injected clients
│ └── payment-service/ # DI container, injected gateways
├── platform/
│ ├── gateway/ # Injects auth, rate limiting
│ ├── config-server/ # Injects configuration to all services
│ ├── feature-flags/ # Injects runtime behavior changes
│ └── mesh-proxy/ # Injects retry, circuit breaker, tracing
└── infrastructure/
├── task-queue/ # Injected broker (Redis/RabbitMQ)
└── observability/ # Injected tracing, metrics
# Start the platform
docker-compose up
# All services boot with injected configuration
[CONFIG] user-service loaded 15 config values from config-server
[MESH] Sidecar proxy attached to user-service
[FLAGS] Feature 'new-login-flow' enabled for 10% of users
# Make a request
curl http://localhost:8080/api/orders
# [GATEWAY] Auth injected: user-123
# [MESH → order-service] Retry 1/3
# [MESH → payment-service] Circuit breaker: CLOSED
# [TRACE] Span: gateway -> order -> payment (250ms)
- See every DI concept working together
- Swap any component (database, queue, payment provider) without code changes
- Run the entire platform in test mode with all mocks injected
- Deploy to production with real implementations injected via configuration
Learning milestones:
- Individual services work with DI containers → You’ve mastered in-process DI
- Service mesh injects cross-cutting concerns → You’ve mastered infrastructure DI
- Feature flags change behavior at runtime → You’ve mastered dynamic DI
- Full integration tests run with mocks → You’ve mastered testable architecture
- You can explain why DI matters at every level → You’ve truly mastered DI
Summary: The DI Learning Path
- Feel the pain → Project 1 (Refactoring)
- Build the machinery → Project 2 (DI Container)
- See it in action → Projects 3-11 (Various patterns)
- Master testing → Project 12 (Mocking Framework)
- Scale it up → Projects 13-18 (Distributed patterns)
- Put it all together → Capstone (Microservices Platform)
After completing this path, you won’t just “use” DI—you’ll understand it at a fundamental level. You’ll know when to use a container, when to skip one, how to design for testability, and how DI applies from single functions to distributed systems.
The key insight: DI is not about frameworks. It’s about designing code where dependencies flow inward, are explicit, and can be swapped. Everything else—containers, annotations, decorators—is just convenience on top of that principle.