Dynamic Backend System
New software projects can easily get very complex and are usually developed on a strict time frame. Consequently, it is efficient to reuse parts of previously developed systems, for a new system. At the same time, the development is often split over various teams working on different parts of the project. To make it possible to reuse code from previous projects, and also incorporate code developed by other teams, the APIs are split into two layers: a front end and a back end. In the Qt Interface Framework, the front-end API is called a feature, as usually a specific class is responsible for a specific feature area, such as ClimateControl, which controls the climate feature area.
To function correctly, every feature needs to have a back end connected to it. This back end must implement a corresponding feature back-end interface. Only then, you can create a valid connection between the feature and its back end.
Usually, every feature has only one back-end interface class, which the back end needs to implement for the feature to work. Every back-end interface is derived from QIfFeatureInterface, which provides generic functions and signals that every feature needs, such as error handling.
The back-end implementations are grouped together and implemented inside a Qt plugin. This makes it easy to provide multiple back ends simultaneously and switch the back end at runtime. These back-end plugins are loaded through QtInterfaceFramework. A plugin can provide back-end implementations for multiple features; there is no need to create a separate plugin for each feature.
Qt Interface Framework also distinguishes between two types of back ends:
- production - On a production system, you want to have only production back ends running.
- simulation - During the development phase, it may be useful to have a simulation back end that you can use for front-end development, until the back-end services are usable.
Qt Interface Framework uses a simple naming scheme to identify whether a plugin provides simulation or production back ends. Every simulation plugin needs to have either "simulation" or "simulator" in its name. Alternatively, you can also set the "simulation" key in the plugin's metadata. This is especially useful for static plugins.
The QtInterfaceFramework module provides all the classes that are needed to glue the parts together. In addition to providing the base classes like QIfAbstractFeature or QIfServiceObject, this module also provides the QIfServiceManager, responsible for loading the necessary back-end plugins.
The QIfServiceManager is the central part of QtInterfaceFramework, keeping a record of all the available back ends and their exported interfaces. The service manager scans through all available plugins and their accompanying metadata. This process gives it the ability to only load plugins that a feature requires, to reduce the startup time. All of this information is collected in the service manager in the form of a model, which enables developers to choose the plugin they want to use.
The ServiceObject concept keeps the features flexible, and makes it possible to switch between back ends at runtime. A QIfServiceObject is a handle, which the feature uses to connect to the correct back-end interface. This handle provides methods to query the back-end interfaces available, which the ServiceObject implements. ServiceObjects automatically wrap around plugins, making it possible to share the ServiceObject between multiple features and to explicitly select which back end to use for your feature instance.
Based on the diagram above, the ServiceObject is the handle for a specific plugin. Feature A and Feature B both use the same ServiceObject, which returns an instance of Feature_A_Interface for Feature A and Feature_B_Interface for Feature B. The Feature classes derive from QIfAbstractFeature; the back-end interfaces derive from QIfFeatureInterface.
In contrast to the normal QIfServiceObject, which represents a handle to a back-end plugin, the QIfProxyServiceObject doesn't need a plugin to work. It can be instantiated on the application side and filled with any QIfFeatureInterface derived class. QIfProxyServiceObject is useful for scenarios when a back-end implementation of a feature should not be done inside a separate plugin, but inside the application's code base itself.
ProxyServiceObjects are also used for models that are properties of another feature. For more details, see Models.
Typically, all features use the auto discovery mode. From QML, you can set the QIfAbstractFeature::discoveryMode property; from C++, you can start this with QIfAbstractFeature::startAutoDiscovery(). This property asks the QIfServiceManager for all the back ends available, that implement the required interface for your feature. The manager then chooses the first matching back end and connects the feature to it. QIfAbstractFeature always asks for production back ends first; if none are available, it falls back to a simulation back end. This behavior can be controlled using the QIfAbstractFeature::discoveryMode, that defaults to QIfAbstractFeature::AutoDiscovery. The resulting back-end type can be retrieved via QIfAbstractFeature::discoveryResult. After the feature has loaded a back end successfully, the QIfAbstractFeature::serviceObject property holds the loaded ServiceObject and QIfAbstractFeature::isValid returns
Based on the Climate Control QML Example, the detailed connection order would be as follows:
- A ClimateControl element is created in QML.
- ClimateControl calls QIfAbstractFeature::startAutoDiscovery upon completion.
- QIfAbstractFeature::startAutoDiscovery queries QIfServiceManager for all back ends available.
- QIfServiceManager searches for all plugins available and the interfaces they implement; this search is only done once.
- QIfAbstractFeature accepts the first QIfServiceObject and connects to the corresponding interface.
- The ClimateControl element is ready to be used.
If you don't want your feature to use the auto discovery mechanism, set the discoveryMode to QIfAbstractFeature::NoAutoDiscovery. After that, the feature won't search for a back end anymore, and you need to assign a ServiceObject manually.
For features like the climate control example, the auto discovery mechanism is fitting, as there is usually a 1:1 mapping between a feature and a back end which provides the implementation for that feature. For more generic interfaces, like a media player, this might not be sufficient: you could control a built-in media player back end with this, but you might also want to control the media player running on your mobile phone over Bluetooth.
For this to work, first, you would need to discover the devices available and then pass the ServiceObject of the selected device to the media player interface. The discovery of the available mobile phones can be done using a DiscoveryModel. This provides you with a ServiceObject for each device found. The concept of a discovery model is not limited to mobile phones, it can be used for all back ends that are not hard wired to the system, like internet services or controlling multiple rear seat systems.
Zones are a standard way to provide a single API for multiple points. For instance, a climate control inside a house can be per room e.g. for the living-room and the bathroom. Those rooms act as a zone. The same concept applies to:
- addressbooks for home and corporate use
- several vehicles inside a fleet management system
- windows, mirrors, air-conditioning inside a car
- and more...
Technically, a zoned feature consists of multiple instances of the same feature, a top-level instance which provides the zone-independent API and access to instances for a specific zone. The feature needs to derive from QIfAbstractZonedFeature and implement createZoneFeature() to provide zone-specific instances.
The top-level interface can provide system wide settings. For example, the outside temperature and the weather-forecast. In contrast, the zoned interface provides per-zone functions, such as the desired room temperature.
Building a zoned feature requires the back-end interface to be derived from QIfZonedFeatureInterface. This class provides the back end with an interface to enumerate the zones available. This interface also includes the necessary QIfZonedFeatureInterface::initialize method to initialize any properties.
© 2023 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.