Service Lifetimes
A service lifetime determines how long a resolved service instance is reused.
Mach supports three lifetimes:
- Scoped
- Singleton
- Transient
Choosing the right lifetime depends on whether a service should be shared across the application, reused within a request, or recreated each time it is resolved.
Scoped
Section titled “Scoped”A scoped service has one instance per request scope.
Register it with addScoped():
mach::AppBuilder builder;
builder.addScoped<UserRepository>();
auto app = builder.build();If the same scoped service is resolved multiple times while processing one request, Mach reuses the same instance.
A later request receives a different instance.
Scoped services are useful for state that should be shared during one request but must not be shared between requests.
For example:
class UserService {public: explicit UserService(UserRepository& repository) : m_repository(repository) {}
private: UserRepository& m_repository;};
builder.addScoped<UserRepository>();builder.addScoped<UserService, UserRepository>();Within one request, UserService and any other component that depends on UserRepository receive the same scoped repository instance.
On the next request, Mach creates a new scope and new scoped instances.
Singleton
Section titled “Singleton”A singleton service has one instance for the lifetime of the application.
Register a singleton with addSingleton():
mach::AppBuilder builder;
builder.addSingleton<ConfigurationService>();
auto app = builder.build();Every component that depends on ConfigurationService receives the same instance.
Singletons are useful for services that are safe to share across requests, such as application-wide configuration or stateless shared infrastructure.
class UserService {public: explicit UserService(ConfigurationService& configuration) : m_configuration(configuration) {}
private: ConfigurationService& m_configuration;};
builder.addSingleton<ConfigurationService>();builder.addSingleton<UserService, ConfigurationService>();Both services in this example live for the lifetime of the application.
Transient
Section titled “Transient”A transient service receives a new instance each time it is resolved.
Register it with addTransient():
mach::AppBuilder builder;
builder.addTransient<EmailFormatter>();
auto app = builder.build();Unlike singleton and scoped services, transient instances are not reused across separate resolutions.
Transient services are useful for lightweight services that do not need shared state or identity.
For example:
class NotificationService {public: explicit NotificationService(EmailFormatter& formatter) : m_formatter(formatter) {}
private: EmailFormatter& m_formatter;};
builder.addTransient<EmailFormatter>();builder.addTransient<NotificationService, EmailFormatter>();Whenever Mach resolves a new NotificationService, it also resolves the transient EmailFormatter required by its constructor.
Comparing Lifetimes
Section titled “Comparing Lifetimes”| Lifetime | Instance Reuse |
|---|---|
| Singleton | One instance for the application |
| Scoped | One instance per request scope |
| Transient | A new instance for each resolution |
As a general rule, use the narrowest lifetime that matches the service’s responsibilities.
Lifetime Dependencies
Section titled “Lifetime Dependencies”A service can depend on services whose lifetimes are compatible with its own.
In particular, a singleton cannot depend on a scoped service:
class ApplicationService {public: explicit ApplicationService(RequestService& requestService) : m_requestService(requestService) {}
private: RequestService& m_requestService;};
builder.addScoped<RequestService>();builder.addSingleton<ApplicationService, RequestService>(); // InvalidApplicationService would live for the entire application while RequestService belongs to a single request scope, so Mach rejects this dependency relationship.
Circular Dependencies
Section titled “Circular Dependencies”Dependencies must form a resolvable graph.
Two services cannot depend on each other directly or indirectly:
class FirstService {public: explicit FirstService(SecondService& second) : m_second(second) {}
private: SecondService& m_second;};
class SecondService {public: explicit SecondService(FirstService& first) : m_first(first) {}
private: FirstService& m_first;};This creates a circular dependency:
FirstService -> SecondService -> FirstServiceMach cannot construct either service because resolving one requires the other to already exist.
Circular dependencies should be removed by restructuring the responsibilities or dependencies of the involved services.
Next, you’ll learn how registered services are injected into controllers, middleware, and other services.