Class pollution and the EJB spec
Joseph Marques
jmarques at redhat.com
Sat May 8 23:53:14 UTC 2010
Remember, the Remote API wasn't introduced until RHQ 1.3.0. Thus, all
people ever did was code their *Locals, and then implement them in the
*ManagerBeans.
However, I suppose because we never exposed any of our methods, and
because we never really had up-front standards for our *Locals, the
style and form and names of these methods were grossly inconsistent
across SLSBs (and even sometimes in the same SLSB with different
authors). If we had opened these methods up to remote clients,
customers would have been pulling their hair out trying to use our
remote API.
And so, even though we were strapped for time, we knew we had to fix
these API issues before our first release (otherwise, even if we fixed
them in the future, we'd have to support both the crappy API as well as
the refined API). Accordingly, 40 or so rules and conventions were
adopted to help close the inconsistency gap, which are documented here:
http://www.rhq-project.org/display/RHQ/Design-Remote+API#Design-RemoteAPI-ApproachandConventions
But that was only the first of two major problems with the remote API.
It turns out that our implementations of the JBoss Remoting invocation
handlers were performing double serialization. EJB3 would serialize
results once when the invocation handler's invoke() method would call
across the *Remote boundary of the target SLSB, and JBoss Remoting would
serialize the results again as they were being returned by invoke()
method itself. On top of that, we also wanted to avoid remote clients
getting the classic Hibernate LazyInitializationException when trying to
access data that hasn't been fetched. Thus, we wanted to "cleanse" all
of our data by passing the results from the SLSBs through our
HibernateDetachUtility, which unwraps all of the Hibernate proxies and
replaces them with their actual raw entity counterparts from core/domain.
So the solution was to avoid calling across the remote interfaces
altogether. Our service invocation handlers would instead call across
the locals, which would hand us back object references (as opposed to
invoking serialization). We then take these objects and before
returning them out of the invoke() method, run them through
HibernateDetachUtility. As a consequence of doing this (calling across
the *Local boundary), we had to ensure that clients (CLI and WS) weren't
calling methods that they shouldn't be allowed to (i.e. getOverlord()).
The solution was to only allow method invocations on the *Local if that
same method was declared on the *Remote interface. To do this we would
first lookup the request to see if the target method signature exists on
the *Remote interface, and if it did we look up the same method on the
*Local interface and call that one instead. The invoke method can thus
be distilled as:
Object invoke(InvocationRequest request) {
NamedBasedInvocation nbi = // get from request
String methodName = // get from 'nbi'
Object result = null;
try {
// Lookup the remote first, if it doesn't exist exit with error.
// This prevents remote clients from accessing the locals.
ic.lookup("rhq/" + methodName + "/remote");
Object target = ic.lookup("rhq/" + methodName + "/local");
// do actual invocation
} ...exception handling...
} finally {
result = useHibernateDetachUtility(result);
}
return result;
}
The net effect of this was that all of our *Local interfaces had to
define ALL of the same methods that we exposed in the *Remote
interface. In other words, the Locals were always a superset of the
Remotes. Given this, it was possible to write the Local interfaces by
extending their Remote interface counterparts.
And so it was this late realization of the double serialization that
caused most of the original remote/locals pairs to be written without
using extension...because initially one wasn't necessarily going to be a
subset of the other (but now it's required).
The primary reason for even using the "Local extends Remote" pattern was
to eliminate inconsistencies that might arise if people added a remote
method but then didn't have that same method defined on the local
interface. Granted, the probably of this slipping by us should tend
towards 0 if we have good testing practices in place, such that we
always create unit test methods for everything. But let's be honest
with ourselves - our unit tests are no where near full coverage today
(that should change!) Also, keep in mind that time tables were
extremely tight for getting all of this done for before RHQ 1.3.0 went
GA, and so getting full-coverage testing (for better or worse) was not a
blocker for release. In short, we did the best that we could, given
little time to actually do all of this, needing to make **thousands** of
lines of refactoring changes according to the new code conventions, and
then making sure that the locals were a superset of the remote interface
due to the serialization issue workaround.
In any event...that release is over and done with, and we have a little
more time now to breathe and figure out what the right solution for us
is. Do we want to use the "Local extends Remote" pattern across the
board? Or do we want to revert things back such that all remote methods
need to be duplicated in the local interfaces? I could go either way.
On 05/07/2010 12:24 PM, Jay Shaughnessy wrote:
> We actually used to do it the way you're suggesting until someone, can't
> remember who, suggested that we pull the remote decls out of the local
> in favor of the "implements remote" approach. No one could remember why
> we had done it the original way but perhaps it was for JDK5 compatibility.
>
> Of course the new approach looks much cleaner and is easier to deal
> with. If there is any we can keep it this way it would be nice.
>
> On 5/7/2010 7:22 AM, Heiko W.Rupp wrote:
>
>> Hi,
>>
>> I see the following the following pattern being used more and more in our code
>>
>> @Stateless
>> public class MyBean implements MyBeanLocal {
>> ...
>> }
>>
>>
>> @Local
>> public interface MyBeanLocal implements MyBeanRemote
>> {
>> public void foo();
>> ...
>> }
>>
>> @Remote
>> @Webservice
>> @SomethingElse
>> public interface MyBeanRemote
>> {
>> @WebMethod public void foo();
>> ...
>> }
>>
>> I am currently seeing the following issues with that: on jdk15, @Webservice, @SomethingElse need to be on the classpath of clients using MyBeanLocal for compilation.
>> Actually (but I have not found an example forbidding it), I am not sure if the pattern above is portable, as the spec does not talk about
>> the semantics of such inheritance of @WebService and @WebMethod (as opoosed to the inheritance of @TransactionAttribute)
>>
>> So I think it would be better in the future not to use this above pattern - the IDE actually allows to easily pull up a method form
>> a session bean class into both interfaces if needed.
>> Heiko
>>
>>
>>
>>
> _______________________________________________
> rhq-devel mailing list
> rhq-devel at lists.fedorahosted.org
> https://fedorahosted.org/mailman/listinfo/rhq-devel
>
More information about the rhq-devel
mailing list