Dependency Injection
Overview
Dependency Injection is giving classes the instances that depends on rather than letting these classes creating their dependencies inside.
Dependencies are usually interfaces (contracts) not classes.
Importance
- Decouple components.
- More testable, you can inject mocks.
Example
PlanControllerdepends onIPlanServiceinterface.- An implementation of the
IPlanServiceinterface is injected, given, provided to PlanController.
C#
public class PlanController(IPlanService planService) : ControllerBase, IPlanController
{
// planService instance is injected to PlanController
// Without dependency injection:
// private readonly IPlanService _planService = new PlanService(); // Concrete creation (tightly coupled)
}
// Which instance should be passed? configured in Program.cs:
services.AddSingleton<IPlanService, PlanService>();Java
@ApplicationScoped
class DefaultPlanController implements PlanController {
@Inject
PlanService planService; // PlanService is the refernce type (the interface), planService is the instance (the object) which it's value is of type DefaultPlanService which is an implementation to that interface.
// PlanService planService = new DefaultPlanService(); // This is dependency concrete creation (tightly coupled)
}Go
type planHandler struct {
planService service.PlanService
logger logs.Logger
}
func NewPlanHandler(service service.PlanService, logger logs.Logger) PlanHandler {
return &planHandler{planService: service, logger: logger}
}
// an instance of type PlanService is injected to PlanHandler in main (wiring)TypeScript
@Controller('plans')
export class DefaultPlansController implements PlansController {
constructor(@Inject('PlansService') private readonly plansService: PlansService) {}
}
// Definition in factory.ts
@Module({
imports: [],
controllers: [
DefaultPlansController,
],
providers: [
{ provide: 'PlansService', useClass: DefaultPlansService },
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AppMiddleware).exclude('/health', '/swagger', '/api-docs').forRoutes('*path');
}
}Python
def get_plan_service() -> PlanService:
from infra.factory import App
return App.plan_service
@cbv(router)
class DefaultPlanRouter(metaclass=ProtocolEnforcer, protocol=PlanRouter):
def __init__(self, plan_service: PlanService = Depends(get_plan_service)):
self.plan_service = plan_service
# Definition in factory.py
from feat.plan.plan_service import DefaultPlanService, PlanService
class App():
plan_repo: PlanRepo = DefaultPlanRepo()
plan_members_repo: PlanMembersRepo = DefaultPlanMembersRepo()
user_repo: UserRepo = DefaultUserRepo()
plan_service: PlanService = DefaultPlanService(plan_repo=plan_repo, plan_members_repo=plan_members_repo, user_repo=user_repo,See
- Dependency injection in PlanController in: C#, Java, Go, TypeScript, Python
