Providers¶
Introduction¶
Providers are the core of waku dependency injection system.
The idea behind a provider is that it can be injected as a dependency into other provider constructors,
allowing objects to form various relationships with each other.
waku responsibility is to "wire up" all the providers using the DI framework and manage their lifecycle.
This way you can focus on writing your application logic.
Dependency Injection¶
waku is designed to be modular and extensible.
To support this principle, it provides a flexible dependency injection (DI) system that integrates seamlessly
with various DI frameworks. waku itself acts as an IoC container,
allowing you to register and resolve dependencies using the modules system.
Note
waku uses the Dishka IoC container under the hood.
All provider lifecycles and dependency resolution are handled by Dishka.
What is Dependency Injection?¶
Dependency Injection (DI) is a design pattern that addresses the issue of tightly coupled code by decoupling the creation and management of dependencies from the classes that rely on them. In traditional approaches, classes directly instantiate their dependencies, resulting in rigid, hard-to-maintain code. DI solves this problem by enabling dependencies to be supplied externally, typically through mechanisms like constructor or setter injection.
By shifting the responsibility of dependency management outside the class, DI promotes loose coupling, allowing classes to focus on their core functionality rather than how dependencies are created. This separation enhances maintainability, testability, and flexibility, as dependencies can be easily swapped or modified without altering the class's code. Ultimately, DI improves system design by reducing interdependencies and making code more modular and scalable.
Manual DI Example
Here, a MockClient is injected into Service, making it easy to test Service in isolation without relying
on a real client implementation.
What is IoC-container?¶
An IoC container is a framework that automates object creation and dependency management based on the Inversion of Control (IoC) principle. It centralizes the configuration and instantiation of components, reducing tight coupling and simplifying code maintenance. By handling dependency resolution, an IoC container promotes modular, testable, and scalable application design.
With the power of an IoC container, you can leverage all the benefits of DI without manually managing dependencies.
Providers¶
Provider is an object that holds dependency metadata, such as its type, lifetime scope and factory.
In waku, there are five types of providers, one for each scope:
Each provider (except Contextual) takes two arguments:
source: type or callable that returns or yields an instance of the dependency.provided_type: type of the dependency. If not provided, it will be inferred from the factory function's return type.
Scopes¶
waku supports four different lifetime scopes for providers, inspired by
the service lifetimes
from .NET Core's DI system.
Transient¶
Dependencies defined with the Transient provider are created each time they're requested.
Scoped¶
Dependencies defined with the Scoped provider are created once per dependency provider context entry and disposed
when the context exits.
Singleton¶
Dependencies defined with the Singleton provider are created the first time they're requested and disposed when the
application lifecycle ends.
Object¶
Dependencies defined with the Object provider behave like Singleton, but you must provide the implementation instance
directly to the provider and manage its lifecycle manually, outside the IoC container.
Contextual¶
The Contextual provider enables you to inject external objects that originate outside the DI container directly into your
dependency graph. This is particularly valuable for framework-specific objects like HTTP requests, database transactions,
or event data that have their own lifecycle managed externally.
When to use Contextual providers:
- Framework integration: Inject HTTP request objects, user sessions, or authentication contexts
- Event-driven scenarios: Process queue messages, webhooks, or callback data
- External resources: Integrate database transactions, file handles, or network connections managed by external systems
- Per-request data: Handle any data that varies per request/operation and originates from outside your application
How it works:
- Declare the contextual dependency using the
contextualprovider in your module - Use the dependency in other providers just like any regular dependency
- Provide the actual value when entering the container scope using the
context=parameter
The contextual provider accepts two arguments:
provided_type: The type of the dependency to be injectedscope: The scope where the context is available (defaults toScope.REQUEST)
Slightly more realistic example:
Consider building a web application with FastAPI where you need to inject the current request into your service layer. Here's how you can accomplish this:
Important
In this example, the contextual provider and waku itself are used to manually inject the current request into the UserService.
However, in real-world applications, you should use the Dishka FastAPI integration to inject the request automatically.
This pattern is essential for integrating with web frameworks, message brokers, and other external systems where objects have lifecycles managed outside your application.
Where and how to inject dependencies?¶
To inject dependencies with waku, you need to:
- Register them as
providerswith the desired scope in modules. - Identify your application entrypoints and decorate them with the
@injectdecorator for your framework. Consult the Dishka integrations section for your framework to find out how to do this. - Add dependencies as arguments to your entrypoint signature using the
Injectedtype hint.
Next steps¶
For advanced features and customization options, refer to the Dishka documentation.