How should I use Guice (or any other injector) inside my bundle?The short answer is: don't.
The slightly longer answer follows hereafter.
Injectors like Spring and Guice were invented because around 2000 large monolithic applications were brought down to their knees because of their internal coupling. Even though a lot of code was written as POJOs, the instantiation and configuration of those POJOs caused massive coupling, making the app brittle and hard, sometimes even impossible, to evolve. Injectors were created to concentrate that coupling (and the related initialization ordering) in one place so the rest of the code base had significantly reduced coupling. Since coupling and complexity have an exponential relation this massively reduced complexity in the application code. A reduction in complexity that more than offset the accidental complexity of the injectors (XML!).
However. In a proper OSGi application, the problem of a big monolithic app just does not exist! A bundle that would really need Guice or Spring is almost by definition not a proper bundle. The reason is that a proper bundle is cohesive, it only contains related parts. Because it is cohesive, most of its coupling is inside and a proper bundle only exposes a tiny part of its internals with well defined OSGi µservices, either registering them or depending on them.
The consequence of this cohesion is that the complexity landscape changes. In this landscape, the accidental cost of an injector no longer weighs up to a reduction in overal complexity as it did for monoliths. This means that good old Java with the new operator works surprisingly well, inside a cohesive bundle it lost a lot of its bad side effects. So plain old Java, as James G. intended it, is actually the best for code inside a bundle. With plain old Java, the IDE is your friend again! You can navigate and refactor at will since Eclipse is aware of your complete type tree inside a bundle, nothing is hidden behind the injector. As long as an implementation class is inside your bundle, you can use it since your IDE knows all its dependencies. That is, if you want to delete it or rename it, go ahead, since all possible dependencies are inside your bundle. Therefore, an injector just creates extra boiler plate, unnecessary barriers, and isolates the type tree while no longer providing an actual benefit.
So if you feel that you need an injector inside a bundle, you should probably redesign that bundle, it is highly likely you cram unrelated parts in it.
Now, so far this was about the bundle internals. The next story is of course the external dependencies. A proper bundle consists of a set of (DS) components. Each component is configured by DS from Configuration Admin, it has its own life cycle based on its configuration and (optionally configured) dependencies. With the annotations, the overhead of using this is quite minimal. The following is a complete component that registers one service when its two service dependencies are met.
@Component public class FooComponent implements Foo { private Bar bar; @Activate void activate( Mapmap) { ... } void foo() { bar.bar(); } @Reference void setLog( LogService log) { } @Reference void setBar( BarService bar) { this.bar = bar; } }
The magic of these components is, is that suddenly you can make any object an active component. Once it is a component, you get configuration data for free (you can even instantiate multiple in of them under Configuration Admin control), you get full dynamic dependency management, and also service registration is for free. These OSGi service components are as fundamental a shift in computing as objects were in the nineties. Ok, maybe we could reduce this tiny amount of boilerplate (and we are working on some additional simplifications) but surely not by much.
So to summarize. I think the accidental complexity of an injector inside a cohesive bundle is not balanced by reduced overall complexity. The DS spec strikes a fine balance by using injection for the external (dynamic) dependencies but assuming normal off the shelf Java for internal code. So, for a change, try unadulterated Java inside your DS components, the new operator is actually quite handy!
Peter Kriens
@pkriens
Peter Kriens
@pkriens
While I really like the behaviour of DS in regards to OSGi services and config admin I think there are some things missing that typical business aplications would like to use,
ReplyDeleteThe most import thing is extensibility. DS (as far as I understood) is mostly limited to what it already can do. Let us look at some typical cases that I would like to support:
- Exposing and using web services
- JPA and Transaction support (@Transactional)
- Authorization support (@RolesAllowed)
I fully understand that it would be out of scope of DS to directly support these features. What I would like to have though is an extension model so people can plug in support for these things. CDI supports the portable extension model which is a great way to add such things.
Technically what I think is missing in DS is support for proxies between the classes. So for example if I define a service in DS and want to call a Repository class handling jpa then I need some kind of proxy in front of the Repository to handle the container managed transactions. This is one of the things where "new" just does not work (unless you use byte code enhancement).
Is there some concept in DS to support such things?
"JPA and transaction support"
DeleteJPA is not OSGi friendly at all. It should not be used in an OSGi environment (or in any environment). JPA wants to solve issues instead of the programmer. Issues that it should not and cannot solve. There are great alternatives like Liquibase and Querydsl. They do not offer magic (like schema generation from annotations, caches, Entity object lifecycle) but a solution for you.
"@Transactional"
This should be solved with functional interfaces, not annotations. See my reasons at https://github.com/everit-org/transaction-helper (Why not annotations, interceptors or other magic).
"Authorization support (@RolesAllowed)"
The same as with @Transactional. This can be solved well with functional interfaces and an @Reference.
I can understand your concerns about proxies or weaving but I would really miss the simplicity "at the source code" level of things like @Transactional. Additionally these annotations are described in the JEE standard. So I think it would be really great to support them. We can not simply say that the approaches described in the standards are all bad and we provide completely different solutions that people have to live with. If we really want OSGi to prosper in the enterprise world we have to support the standards as well as possible.
DeleteStill thanks for sharing this. Your approach makes it possible to have similar functionality in DS. So while I do not agree that it should be the only solution it is at least a path to go.
In theory I agree, in practice it is not so simple. I will list my points.
ReplyDeleteMany software libraries providing real business value are often not coded to handle dynamic behaviour that OSGi is tackling and providing solutions for. They simply start to break down if you expose them to dynamicity of services or worse dynamicity of classloaders. They often also because of using singletons will force you to put them in one bundle. Thus, If you want to use this software then the only viable business path is to put them inside the bundle and create a monolithic application.
Spring and other good quality injection frameworks provide a know-how that is invaluable as it helps to use the software components from those libraries in a proper manner. Often, Spring framework will provide best practices of how you should use it. It will help developers avoid mistakes in the first place. The value of Spring framework is not just about DI it is about the know how, about providing BOM (Bill of Materials - pom file) that certifies that those dependencies have been proven to work well together. It is about using AOP to provide some separation of concerns. Others will complain that I do not list more of the value added provided by Spring and they will be correct.
Finally, it is easy to give the answer from the expert level. You did admit to a fundamental difference at looking how to bring people to write modular application in the foreword written for Java Application Architecture book by Kirk Knoernschild. It is a journey and DI framework have a good place at the beginning of this journey.
It is a long journey before the companies can afford to improve existing libraries that they are forced to use. Only after those libraries are improved they can have fully modular applications as you advertise. In the meantime they need to deal first with lack of understanding what software versions they can safely use together, get the versioning of their dependencies under control, slowly improve the libraries to get rid of singletons, improve the design of the libraries themselves so they can handle dynamicity, enforce semantic versioning, etc. The list is long and we as a whole are still struggling with proper versioning of the code.
Dependency Injection frameworks like Spring provide value. If OSGi community does not make it easy to use those frameworks then some people will be faced with a choice of Spring or OSGi. They will choose for very good business reasons to use DI framework only and ditch OSGi. Those people giving up OSGi to have help from Spring will postpone their journey to fully modular application until another player (Oracle?) will come up with an easier path from monolithic applications to fully modular applications.
In theory I agree with this blog post. In practice I wish there was a shared community effort to bring OSGi and Spring together to help people in their journey of getting fully modular applications. Spring DM is dead and the decision Spring or OSGi will be forced upon some developer teams.
This is my opinion only and it does not have to coincide with the opinion of my colleagues and my employer.
best regards,
Radoslaw Szymanek
"Many software libraries providing real business value are often not coded to handle dynamic behaviour that OSGi is tackling and providing solutions for"
DeleteWhen someone changes from other programming language (like C++) to Java, everything has to be reimplemented. The situation is a bit better now as the syntax is the same. Some of the libraries are good as they are. Not many of them.
The ones, who try to use monoholitic libraries in an OSGi environment will feel pain and will say that OSGi juts hinders. The ones, who try finding workarounds, will feel the same.
Some sentences of the chapter "Marshes, Bogs, Swamps, and Other Messes" of the book "The Clean Coder" describes the situation pretty well. Writing workarounds for monoholitic libraries makes mess.
Companies should decide if they really want to change to a new stack or stay with the monoholitic architecture. A middle solution can cause more pain than joy.
" until another player (Oracle?) will come up with an easier path from monolithic applications to fully modular applications"
I heard that Jigsaw will be part of Java 9. The only issue is that I had heard the same about Java 8 and Java 7 before :). They cannot do it, they will not be able to do it. There is no easier way if we want to have a clean code.
I met a guy who said that it was a big mistake to make the Linux Kernel modular with version 2.0. This opinion seems to be funny from this distance. I believe that in 10 years sticking with monoholitic solutions like Spring and Java EE will look as funny as staying with the monoholitic Linux kernel 1.x looks now.
Hi,
DeleteNot many companies can afford to ignore the value of existing libraries just because they force monolithic design. In my view, the companies need to figure the road that slowly but surely allows to migrate from monolithic applications to modular ones while providing value to their customers continuously during this journey. Modular applications requires a long term investment strategy financed by monolithic solutions for the client in the short-medium term.
I do not dispute the need for modular solutions. However, if the path to them is not made easier by OSGi community then the Java software community as a whole will be moving slower in a more painful manner towards modular software.
I recognize huge value provided by OSGi. I also recognize huge value provided by Spring and its library ecosystem. I will not be successful in explaining the value of OSGi if I was taking a stance - throw away most of the existing libraries. The developers I know do not have such a luxury.
best regards,
Radek
"Not many companies can afford to ignore the value of existing libraries just because they force monolithic design. In my view, the companies need to figure the road that slowly but surely allows to migrate from monolithic applications to modular ones while providing value to their customers continuously during this journey."
DeleteI completely agree. The question is in the migration techique.
Many companies start migration in the way that they try to use JEE technologies in OSGi. The issue is that it simply does not work. We tried it. We used JPA, JSF, Blueprint in OSGi and it caused more pain than happiness.
I think the migration must be done in a way that the old technologies are completely separated from the OSGi related ones. They might live inside one JVM, but either an embedded OSGi container must be used in a Java EE server or an embedded Java EE server must live in an OSGi container. The only connection point between the two architecture should be a dependency-less API.
This path costs more in the beginning as a complete stack must be built on top of OSGi that already existed in the old architecture. Within years (if not months) that money will come back as developers do not have to spend their time fighting against the issues of the forced marriage of monoholitic technologies and OSGi.
Thanks for this Peter, great inside. I have a couple of clarifications for some scenarios I face.
ReplyDelete1. On intra-bundle dependencies, The reason I went massive with Guice, is because of Xtext. When I first saw the XText modules which wired the whole framework, and how easy it was to swap out (Override a module!), I fell in love. (At least for the intra-bundle wiring). And then there are providers as well, so lazy loading with provider.get(), back to new and factories then?
2. Singletons.
How do you suggest dealing with singleton classes. Sometimes you just want a single instance of a class. Perhaps DS can deal with this?
3. Isn't your service static?
One rule I apply for myself when applying a pattern, is to ask if a service (class) actually maintains some sort of state hence lifecycle. With no-side-effects. If it just processes stuff, and returns a morphed output of the input. then for me it's a static, not a service and simply accessible as a static method. With this approach, I have managed to drastically reduce the amount of 'statefull' code accross my bundles. (Nothing to do with DS I guess, but I am interrested in your view on this in relation to OSGI, as cross-bundle references will exist beyond DS, which might break the 'cohesive' contract you talk about).
4. Hierarchies,
what's your view on hierarchies of services, consuming each other. I am sure order, sequence etc.. matters. Is there a good pattern for consuming services to wait around, blocking upstream services?
5. Regarding @Component, @Reference. Will this work with Equinox in it's current release (Kepler, Luna, Mars)?
Thank You for the nice article.
1. Multiple implementations.
Both bundle foo.bar.a and foo.bar.b implement IFooBar.
How does DS deal with this?