Hi Kai,
The boostrap API and the abstraction it provides is something we need to introduce in order to make our services framework independent and avoid a vendor lock-in.
To be more exact, let me use the example. Imagine that we implement a device management service based on Leshan (kind of thing we are doing in Rhiot). The service should be provided in a form of the library, which can be started and stopped.
DeviceManagementService deviceService = new DeviceManagementService();
deviceService.start();
deviceService.stop();
"Start" method starts embedded Leshan server, initialized database connection pool (used by Leshan to store devices in the registry), connects to the cache cluser (like Hazelcast or Infinispan, used to cache devices information and metrics, for performance reasons) and performs all the other kind of an initialization needed by the device service. When a service is supposed to be shut down, we call the "stop" method. It makes sure that all of those resources have been properly closed. Also the start/stop sequence has to be properly ordered, not accidental. Our DeviceManagementService can be used out of the box without any additional configuration - it will use default settings, default persistence providers, etc.
Now imagine that the company using our DeviceManagementService as a part of their IoT cloud offering, would like to add some non-trivial extension to it. Our platform be easily extensible as this is required for its wider adoption. So for example:
- company Foo would like create an extra module based on Apache Camel and its SAP connector, so the information about the devices is immediately available in the SAP system
- company Bar would like to provide its own Leshan devices registry implementation
- company Baz would like to connect all the events related to the devices to its Apache Spark cluster and perform real-time analysis on it, then perform some actions on the Leshan server based on the data analysis
In all those cases we need to:
a) create proper callback interfaces in DeviceManagementService that could be implemented to achieve customization on the level of the client code
b) client has to provide the customized callback code to the classpath of the application
c) somehow wire this custom code to the core of our service
The thing is that c) is very different depending on what kind of application framework we would like to use. For example if we are using Spring Boot, the complete customized application for company Baz could look like:
public class BigDataAwareDeviceManagement {
@Bean(init = "start", destroy = "stop")
DeviceManagementService deviceManagementService(DeviceEventCallback deviceEventCallback) {
return new DeviceManagementService(deviceEventCallback);
}
@Component
static class SparkDeviceEventCallback implements DeviceEventCallback {...}
}
However the same application written in OSGi will be much different. For example DeviceManagementService could be deployed as a one OSGi bundle, while the SparkDeviceEventCallback could be a second bundle with OSGi service exposed.
If we would like to make DeviceManagementService framework-agnostic, we should relay on the vendor-neutral API when accessing the collaborators (like DeviceEventCallback implementations). We could name it BeanRegistry and it could be used inside DeviceManagementService as follows:
class DeviceManagementService {
DeviceEventCallback deviceEventCallback;
...
void start() {
deviceEventCallback = beanRegistry.beanByType(DeviceEventCallback.clas);
}
}
Then we could provide various implementations of the BeanRegistry, like SpringBeanRegistry or OsgiServiceBeanRegistry. With this approach, we can access beans implementing the extensions to our services in a framework-agnostic way.
The same principle applies to the configuration management. OSGi uses Configuration Admin Service, Spring Boot uses its properties/YAML files, Vert.x system properties and so forth. If we would like to make our services configurable, we should provide configuration access abstraction, which in turn should delegate to the proper configuration management system (OSGi, Spring, etc):
class DeviceManagementService {
private static final int DEFAULT_TIMEOUT = 5000;
...
void start() {
int maxDevicesCacheSize = configurationResolver.intProperty("devices.cache.size.max", DEFAULT_TIMEOUT);
}
}
We can also decide to create a library, not a service implementation runnable out-of-the-box. In such case it will be an end-user responsibility to assemble the service using our library with the gazillions of setters and getters. Which considering all the possible object collaborators that will be included in service like device management, will soon become a configuration nightmare for the end-users. I'm strong believer that the developer adoption is one of the key factors when designing the middleware, so I would prefer to avoid following pure-library path and provide a "boostrap API" instead.
Does it make sense to you?
Cheers!