Over time, I've become convinced that part of the problem is the name: services. The web-services guys have stolen our name; talking about services today lights up your conversation partner's neurons of: heavy, slow, XML, complicated, and other neurons you'd prefer to stay calm. Though web-services and OSGi services have the same underlying architecture for decoupling, their execution, purpose, and overhead differs day and night. A web-service communicates with a party outside your process, an OSGi service communicates always within the same process, without any overhead. Calling a method on a service is as fast as calling a method on an object, services are almost as lightweight objects. There is some overhead in signalling the life cycle of the service for the providers and the consumers but in runtime all that overhead is gone.
Though web-services have given the term service the connotation of heavy-weight, we're also to blame by not being ambitious enough. It is not until very recent that I've come to see how much we missed the part that has later been filled in by Service Binder, Declarative Services, iPOJO, Spring DM, and Blueprint. The original model of handling your service dependency manually is and was broken from the start. Sadly, I actually recall discussing moving this responsibility to the framework but it was deemed too hard and we did not have enough time. Due to the lack of built-in support for automatic service dependency handling we created the image of services being awkward constructs. Messing around service references and service trackers did not make it look lightweight at all! However, those days are gone and services are now not only lightweight with respect to performance and other overhead, today they are trivially to use, almost as easy as normal objects, albeit with a lot of built-in power that normal objects lack. With annotations, declaring a service has become trivially to use. For example:
@ComponentUsing the bnd annotations there is almost no cruft in the code. The following bnd file is the only extra file required:
public class ExecutorManager implements Executor {
ExecutorService executor;
LogService log;
public void execute( final Runnable r ) {
executor.execute( new Runnable() {
public void run() {
try {
r.run();
} catch( Throwable t ) {
log.log(LogService.LOG_ERROR, "execution failed", t );
}
});
}
void deactivate() {
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
}
@Reference
void setThreadPool( ThreadPool pool ) {
executor = Executors.newCachedThreadPool(pool);
}
@Reference
void setLog( LogService log ) {
this.log = log;
}
}
Really! And this is not limited to bnd, iPOJO and the Apache SCR runtime annotations provide similar simplicity.
Private-Package: com.example.executor
Service-Component: *
This example is very little code but surprisingly robust in many dimensions. It is perfectly thread safe, all timing issues are managed. The Executor service is not registered before the Log Service and the Thread Pool service are available. And when one of these services go away, everything is correctly handled. The example is also very open to extensions that are completely invisible from the outside. As a service I can always find out what bundle is using me and have bundle specific options, for example, certain bundles should be limited in the amount of CPU time they can take through the executor.
In the eighties I discovered object oriented programming and quickly fell in love with it. OO caused a paradigm shift; we started thinking differently about how you solved problems. Today it is incredibly hard to imagine thinking without objects because objects have become an intrinsic part of our vocabulary. However, in the eighties we explained objects with C structs that had pointers to an array of methods and then when you send a message to an object it would be dispatched to the correct class method. I recall countless discussions with people that basically didn't see the innovation because they could only see the mechanic description and not the paradigm shift; how objects really simplified problems when you treated them as primitives. I do believe services are similar in this aspect, when you have to worry about Service References and cleaning up, the chores overwhelm the gains. However, when there no more chores to worry about, services are an incredibly elegant design primitive that map very well to domain specific problems.
Now I do realize that "paradigm shift" is a loaded term. In the nineties the paradigm word was heavily abused; for a short time it became the marketing term of choice for many software products. Soon after the abuse the word was ridiculed whenever used, paradigm shifts do not come that often. I am therefore fully aware that I use big words here, but I do believe that services are a similar layer on top of objects as objects were on top of structured programming.
If you look at the recent history of software after OO became mainstream then there are a number of patterns that stand out:
- Listeners
- Factories
- Dependency Injection
So the three aforementioned patterns use exactly the same trick, the difference between the three pattern is the dynamicity. With a listener, the control is from the library to the client. With a factory, the control is reversed, the client takes the initiative and the library is passive. Dependency injection is interesting because in this model both the client and the provider are passive, the DI framework has the initiative. Client and provider activity must be encoded in normal code, the DI framework is oblivious of this. This exactly the reason the service model took some time to integrate with Spring DM, this was the first time the providers and clients became active.
There are, however, four cases when both the client and the provider can be active or passive. What is this fourth case? This is the case where both the client and the provider are active. This is exactly the case that the OSGi service registry covers: active clients and active providers. The service registry fits perfectly in the list of patterns because it also use the interface to separate clients and providers. One could call the OSGi service registry the missing link ...
The OSGi service model does not provide an additional model, it only unifies the factory and listener pattern allowing both of them to exist simultaneously. It now becomes clear why it was such an oversight that we did not add a dependency injection facility until release 4, if we'd had that from the beginning we had covered all cases. However, with Declarative Services, the OSGi does cover all the 4 cases.
An OSGi service therefore unifies the Factory, Listener, and Dependency Injection into a single primitive idea. Because of this unification it also supports situations where both the client and the provider are active. In today's infrastructure this is no longer a luxury or nice feature, it has become a necessity. Clusters, cloud computing, and the interaction with other systems require that software does not fail when dependencies are not met all the time. All those semantics are contained in OSGi services for a very low price, both in performance, runtime costs, and conceptually.
However, the most exciting part of services is that they seem to map so well to many software domains. Maybe this excitement is partly caused by my background that is largely outside enterprise software. Most software I worked on was connected to the real world and the real world just happens to be awfully dynamic. Most of those problems can be more easily solved when services are used as a design primitive.
Trying to convince people to use services as design primitives seems to fly against the idea of abstracting oneself from the OSGi API. In my eternal quest against coupling I fully agree with this sentiment, it is exactly what I always do. However, OSGi services transcend OSGi, I am not promoting the OSGi APIs for using services, that is just the first place where this paradigm has matured and a good place to get experience. What I am promoting is the idea of µServices, the concepts of an OSGi service as a design primitive. Maybe I should start a JSR to introduce µServices to the Java Standard Edition ...
Peter Kriens