Lumiera
0.pre.03
»edit your freedom«
|
Go to the source code of this file.
Per type specific configuration of instances created as service dependencies.
This is the _"Backstage Area"_ of lib::Depend, where the actual form and details of instance creation can be configured in various ways. Client code typically plants an instance of lib::Depend, templated to the actual type of the dependency. This is a way to express the dependency on some interface or service, while not expanding on any details as to when and how this dependency is created. Without an explicit configuration, lib::Depend will automatically create and manage a singleton instance of the type given as type parameter.
A dependency is understood as something we need to perform the task at hand, yet a dependency referres beyond that task and relates to concerns outside the scope and theme of this actual task. The usage site of the dependency is only bound and coupled to the interface exposed as dependency, and the associated contract of a service. Initially, such a dependency is dormant and will be activated on first access. This simplifies the bootstrap of complexly interwoven structures; it suffices to ensure that none of the participating entities actually starts its work before all of the setup and wiring is done.
For that reason, lib::DependInject<SRV> is meant to be used at the site providing the actual service or implementation subtype – not at the site consuming a dependency (through lib::Depend<SRV>). This choice also means that the actual configuration of services is not centralised, and can not be static; it need to happen prior to any service access (on violation error::Logic is raised)
The public configuration mechanisms offered by DependInject address various concerns:
Even when relying on lazy on-demand initialisation, a concrete service implementation typically needs to connect to further services, and maybe even decide upon the actual subclass to be instantiated. By invoking the DependInject<SRV>::useSingleton(FUN) function, a functor or lambda can be installed into the static factory of lib::Depend. Especially, a lambda could be bound into the internal context of the service provider. This function is expected to deliver a heap allocated instance on invocation, which will be owned and managed by lib::Depend<SRV>::factory (A DependencyFactory<SRV>).
Whenever a module or subsystem can be started and stopped, several interconnected services become operational together, with dependent lifecycle. It is possible to expose such services through a lib::Depend<SRV> front-end; this way, the actual usage context remains agnostic with respect to details of the lifecycle. Any access while the service is not available will just raise an error::Logic. This kind of configuration can be achieved by planting a smart-handle of type DependInject<SRV>::ServiceInstance<IMP>
Dependencies tend to hamper unit testing, but global variables and actively linked and collaborating implementations are worse and often prevent test coverage altogether. Preferably dependencies are limited to an interface and a focused topic, and then it might be possible to inject a mock implementation locally within the unit test. Such a mock instance temporarily shadows any previously existing state, instance or configuration for this dependency; the mock object can be rigged and instrumented by the test code to probe or observe the subject's behaviour. This concept can only work when the test subject does not cache any state and really pulls the dependency whenever necessary.
The lib::Depend<SRV> front-end is optimised for the access path. It uses an std::atomic to hold the instance pointer and a class-level Mutex to protect the initialisation phase. On the other hand, initialisation happens only once and will be expensive anyway. And, most importantly, for any non-standard configuration we assume, that – by architecture – there is no contention between usage and configuration. Services are to be started in a dedicated bootstrap phase, and unit tests operated within a controlled, single threaded environment. For this reason, any configuration grabs the lock, and publishes via the default memory order of std::atomic (which is std::memory_order_seq_cst). Any spotted collision or inconsistency raises an exception, which typically should not be absorbed, but rather trigger component, subsystem or application shutdown.
Definition in file depend-inject.hpp.
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/depend.hpp"
#include "lib/meta/trait.hpp"
#include "lib/meta/function.hpp"
#include "lib/sync-classlock.hpp"
#include <type_traits>
#include <utility>
#include <memory>
Classes | |
class | DependInject< SRV > |
This framework allows to (re)configure the lib::Depend front-end for dependency-injection. More... | |
class | DependInject< SRV >::Local< MOC > |
Configuration handle for temporarily shadowing a dependency by a test mock instance. More... | |
class | DependInject< SRV >::ServiceInstance< IMP > |
Configuration handle to expose a service implementation through the Depend<SRV> front-end. More... | |
struct | DependInject< SRV >::SubclassFactoryType< FUN > |
Namespaces | |
lib | |
Implementation namespace for support and library code. | |