Understanding Dependency Injection
Dependency injection (DI) lets classes receive the services they depend on instead of constructing those services themselves.
Mach provides a dependency injection container that manages registered services and supplies them where they are needed.
Why Use Dependency Injection?
Section titled “Why Use Dependency Injection?”Consider a service used by a controller:
class UserService {
public: std::string getName() const { return "Alice"; }};Without dependency injection, the controller would need to create or otherwise obtain UserService itself.
With DI, the dependency is declared in the controller’s constructor:
class UsersController final : public mach::ControllerBase {
public: inline static constexpr std::string_view route = "/users";
explicit UsersController(UserService& userService) : m_userService(userService) {}
mach::Reply<std::string> index() { return ok(m_userService.getName()); }
static void configure(mach::ControllerBuilder<UsersController>& builder) { builder.mapGet("/", &UsersController::index); }
private: UserService& m_userService;};The controller describes what it needs, while Mach is responsible for providing it.
This keeps dependency construction separate from the code that uses those dependencies.
The DI Flow
Section titled “The DI Flow”Using a dependency generally involves three steps:
- Register the service with
AppBuilder. - Declare the service as a dependency of the component that needs it.
- Let Mach resolve and provide the dependency.
For example:
mach::AppBuilder builder;
builder.addSingleton<UserService>();builder.addController<UsersController, UserService>();
auto app = builder.build();Here, UserService is registered with the DI container, and UsersController declares that it depends on it.
When Mach creates the controller, it resolves the registered UserService and passes it to the constructor.
Constructor Injection
Section titled “Constructor Injection”Mach uses constructor injection.
Dependencies are expressed as constructor parameters:
class MyService {public: explicit MyService(Dependency& dependency) : m_dependency(dependency) {}
private: Dependency& m_dependency;};The dependency relationship is therefore explicit in the type itself.
Services can also depend on other services, allowing Mach to resolve an entire dependency graph.
Where Dependencies Are Used
Section titled “Where Dependencies Are Used”Dependency injection is used by framework components that Mach constructs, including controllers and middleware.
For a controller:
builder.addController<UsersController, UserService>();For middleware:
builder.use<LoggingMiddleware, mach::Logger>();In both cases, the additional template arguments declare the constructor dependencies Mach should provide.
The following guides cover service registration, lifetimes, injection, and service-to-service dependencies in more detail.
Next, you’ll learn how to register services with Mach’s dependency injection container.