Skip to content
Download

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.

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.

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.

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.

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.

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>(); // Invalid

ApplicationService would live for the entire application while RequestService belongs to a single request scope, so Mach rejects this dependency relationship.

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 -> FirstService

Mach 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.