This post describes some of the pitfalls when coping with intercommunication between different modules (EAR, WAR, EJB-JAR) deployed in the same Java EE 6 Application Server.
The problem is as follows: you have two modules (let’s take the EARs as an example) that are deployed in the same JVM / Application Server: App1.ear and App2.ear.
service.jar; it’s an EJB-JAR with the business logic of the application,
service-api.jar; it’s an API for the service.jar, so the EJB’s in service.jar must implement this API.
client.jar; it’s the EJB-JAR with the business logic of the client application,
service-api.jar; it’s the same API / interfaces as in App1.ear/service-api.jar. It’s here because you will need to know the API of the
service.jarif you want to communicate with it.
Now when you think about it in terms of the EJB’s and their views, the question is: what kind of view should the App1.ear/service.jar expose? Normally we would need to decide between 5 possible views of the EJBs but let’s forget about those legacy EJB 2.x remote and local interfaces and focus on:
- remote business interfaces,
- local business interfaces and
- no-interface views.
The last one (no-interface view) can be easily rejected as we want to have a bare contract / API we can share between the applications. We definitely don’t want to juggle with actual beans implementation.
So we’re left with the remote business and local business interfaces.
Remote or Local Business Interface?
It’s somehow tempting to think about the local business interfaces as in “local to the JVM” or as in “local to the Application Server“. It also nicely aligns with the fact that the local business interface has the “pass-by-reference” nature.
On the other hand we have remote business interfaces as in “remote JVM” or “remote standalone client“. Very similarly, the “pass-by-value” nature of the remote business interfaces fits in this picture quite well. Moreover, each remote call invokes the serialization and deserialization and these also sounds related with the connection through the network.
After knowing all that, we should go with the local business interface, right? We’re in the same JVM, on the same Application Server instance. And in fact the local business interfaces in such case will work on JBoss AS 6, 7, Glassfish 18.104.22.168 and perhaps few others. But the thing is: EJB specification doesn’t guarantee any behaviour when you use the local business interfaces for inter-modules communication even if those modules are part of the same Application Server / JVM. Those views are used only for inner-module communication (e.g. access EJB-JAR from WAR when they’re are all packaged in one EAR.)
In case of inter-modules communication, you should use the remote business interfaces. Yes, there is an additional penalty of serizalization/deserialization (and perhaps something more?) even if you’re still residing on the same JVM. Additionally, all of your parameters must be Serializable. The container is, of course, allowed to perform any optimization it wants as long as the semantics of the remote business interface is not harmed (e.g. it can bypass the network traffic when caller and callee are on the same machine but it cannot change pass-by-value to pass-by-reference semantics.)
When using remote business interfaces you’re not dependent on the server application vendor when running your application – this behaviour is mandated by the EJB spec, so if it doesn’t work – it’s most likely the application server issue.
There seems to be rationale behind the requirement to use remote business interfaces, especially if you think about clustering your application across multiple nodes and communication between applications that actually resides on different machines.
Local Business Interfaces Behaviour On Different Servers
If you still insist on using the local business interface, you can do so, but you need to remember that this is a non-portable solution and you need to be aware of few pitfalls. Assume you’ve prepared your EARs as described at the beginning of this post (code is available here) and deployed it into one of the applications server.
In short, you are trying to:
- lookup the EJB from the JNDI (using its local business interface),
- cast the result to the expected interface (coming from appropriate
- invoke one of the bean’s methods.
In JBoss 6.1 you’ll be fine without any work because of the hierarchical classloaders. No exceptions, no problems – it just works.
In JBoss AS 7.1 during the casting to the interface type you’ll get a class-cast exception because you’re trying to cast a class from
service-api.jar loaded by App1.ear to
service-api.jar loaded by App2.ear.
Just as a reminder – class instance can be cast into another if the fully qualified names matches and the classloader that loaded the classes is the same. In case of the JBoss AS 7 we have classloaders isolation, so you really can’t make it work out-of-the-box just by preparing the EAR.
There is a JBoss specific way to achieve what we want and its called JBoss Modules. You need to create appropriate directories structure under
$JBOSS_HOME/modules and prepare the
module.xml descriptor. In this way – both App1.ear and App2.ear can define dependency on the same module. The EARs themselves don’t consist of the
service-api.jar – they’re just referencing it from the JBoss Modules.
In case of Glassfish 22.214.171.124 you’ll also get the class-cast exception. I didn’t dig the GF topic any more, but I would expect to have something very similar to the JBoss Modules.
So, even if we know that our application server is supporting local business interfaces between applications in different modules, we still need to be aware of the consequences of such feature. It might work out-of-the-box (JBoss 6) or it might require some additional tasks (JBoss AS 7, Glassfish 3).
I would find those tasks to be completed by the deployer of the application, so someone that knows how particular application server should be configured (I know – it’s very often yourself – just changing the ‘bean provider’ hat to the ‘deployer’.)
Standardized Modules Management
If you think about these differences in the deployment of shared modules you might ask if there is some standard way of defining them which would be the same across multiple server vendors. Well, the more I think about it, the more I am convinced it might be addressed by the Jigsaw project. However, because the Jigsaw was deferred and won’t be a part of Java SE 8 – Java SE 9 is the earliest possible release. This means that Java EE 7 — that is currently under work — won’t address any of the modularity problems. The earliest convenience will be EE 8, so we’re probably talking about 2016 – 2017 when application server vendors will implement EE 8.
Such modular system might be used by application server vendors to standardize the modules installation. Nevertheless, you’d still end with specifying what modules should be installed by the deployer. I really doubt we’ll ever invent such archive type that consists of the application and the modules to be installed (if you think about it – it simply doesn’t make any sense and it mixes the responsibilities of the artifacts.).
Either way, even if Project Jigsaw would address the problem of installation of the modules in application servers, you’ll still end with preparation of such modules apart from your application. The only simplification for the bean provider I can think of, is perhaps some kind of unified way of defining modules dependencies in descriptors or manifest file.
You can find the exemplary application — that creates two EARs that communicates with each other — on my GitHub account here. Just try fiddling with the exposed views of the beans and try deploying it into different servers.
You can find a filled JIRA for adding cross-application local beans accessibility here.