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!