Skip to content
Download

Dependency Injection

Mach includes a dependency injection system for providing services to controllers.

Instead of constructing a controller’s dependencies manually, you can register them with AppBuilder and receive them through the controller’s constructor.

Services are registered on the application builder.

For example:

mach::AppBuilder builder;
builder.addSingleton<UserService>();

addSingleton<T>() registers a single instance of T that can be provided to controllers.

A service only needs to be registered before the application is built. The order in which services are registered does not matter.

Controllers receive dependencies through constructor injection:

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> getUsers() {
return ok(m_userService.getUsers());
}
static void configure(mach::ControllerBuilder<UsersController>& builder) {
builder.mapGet("/", &UsersController::getUsers);
}
private:
UserService& m_userService;
};

When Mach creates UsersController, it resolves UserService from the dependency injection container and passes it to the constructor.

The dependencies required by a controller are declared when the controller is registered:

mach::AppBuilder builder;
builder.addSingleton<UserService>();
builder.addController<UsersController, UserService>();
auto app = builder.build();

The dependency list tells Mach which registered services should be passed to the controller constructor.

All required services must be registered by the time the application is built.

A controller can receive multiple services through its constructor:

class UsersController final : public mach::ControllerBase {
public:
UsersController(UserService& userService, AuditService& auditService)
: m_userService(userService),
m_auditService(auditService) {}
static void configure(mach::ControllerBuilder<UsersController>& builder) {
// ...
}
private:
UserService& m_userService;
AuditService& m_auditService;
};

Register both services and list them when registering the controller:

builder.addSingleton<UserService>();
builder.addSingleton<AuditService>();
builder.addController<UsersController, UserService, AuditService>();

The order of the dependency template arguments must match the order of the controller constructor parameters.

class UserService {
public:
std::string getUserName() const {
return "Alice";
}
};
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> getUser() {
return ok(m_userService.getUserName());
}
static void configure(mach::ControllerBuilder<UsersController>& builder) {
builder.mapGet("/", &UsersController::getUser);
}
private:
UserService& m_userService;
};
int main() {
mach::AppBuilder builder;
builder.addSingleton<UserService>();
builder.addController<UsersController, UserService>();
auto app = builder.build();
return app.run();
}

The controller declares the services it needs through its constructor, while the application builder provides the registrations Mach uses to construct it.


You’ve now covered the core controller workflow: creating controllers, defining actions, binding request bodies, returning results, and injecting dependencies.