Proxied calls not working as expected
- by AndyH
I have been modifying an application to have a cleaner client/server split to allow for load splitting and resource sharing etc. Everything is written to an interface so it was easy to add a remoting layer to the interface using a proxy. Everything worked fine. The next phase was to add a caching layer to the interface and again this worked fine and speed was improved but not as much as I would have expected. On inspection it became very clear what was going on.
I feel sure that this behavior has been seen many times before and there is probably a design pattern to solve the problem but it eludes me and I'm not even sure how to describe it.
It is easiest explained with an example. Let's imagine the interface is
interface IMyCode {
List<IThing> getLots( List<String> );
IThing getOne( String id );
}
The getLots() method calls getOne() and fills up the list before returning.
The interface is implemented at the client which is proxied to a remoting client which then calls the remoting server which in turn calls the implementation at the server. At the client and the server layers there is also a cache.
So we have :-
Client interface
|
Client cache
|
Remote client
|
Remote server
|
Server cache
|
Server interface
If we call getOne("A") at the client interface, the call is passed to the client cache which faults. This then calls the remote client which passes the call to the remote server. This then calls the server cache which also faults and so the call is eventually passed to the server interface which actually gets the IThing. In turn the server cache is filled and finally the client cache also.
If getOne("A") is again called at the client interface the client cache has the data and it gets returned immediately. If a second client called getOne("B") it would fill the server cache with "B" as well as it's own client cache. Then, when the first client calls getOne("B") the client cache faults but the server cache has the data.
This is all as one would expect and works well.
Now lets call getLots( [ "C", "D" ] ). This works as you would expect by calling getOne() twice but there is a subtlety here. The call to getLots() cannot directly make use of the cache. Therefore the sequence is to call the client interface which in turn calls the remote client, then the remote server and eventually the server interface. This then calls getOne() to fill the list before returning.
The problem is that the getOne() calls are being satisfied at the server when ideally they should be satisfied at the client. If you imagine that the client/server link is really slow then it becomes clear why the client call is more efficient than the server call once the client cache has the data.
This example is contrived to illustrate the point. The more general problem is that you cannot just keep adding proxied layers to an interface and expect it to work as you would imagine. As soon as the call goes 'through' the proxy any subsequent calls are on the proxied side rather than 'self' side.
Have I failed to learn or not learned something correctly?
All this is implemented in Java and I haven't used EJBs.
It seems that the example may be confusing. The problem is nothing to do with cache efficiencies. It is more to do with an illusion created by the use of proxies or AOP techniques in general.
When you have an object whose class implements an interface there is an assumption that a call on that object might make further calls on that same object. For example,
public String getInternalString() {
return InetAddress.getLocalHost().toString();
}
public String getString() {
return getInternalString();
}
If you get an object and call getString() the result depends where the code is running. If you add a remoting proxy to the class then the result could be different for calls to getString() and getInternalString() on the same object. This is because the initial call gets 'deproxied' before the actual method is called.
I find this not only confusing but I wonder how I can control this behavior especially as the use of the proxy may be by a third party.
The concept is fine but the practice is certainly not what I expected.
Have I missed the point somewhere?