views:

40

answers:

4

I have two applications communicating via RMI, a slave server (of which there will be multiple) and a master server.

Following good abstract design, I'd like to implement the slave in a way that it doesn't know that when talking to the master, it's using RMI (so that the two apps can also be run inside the same JVM, for instance):

public static void main(String[] a) {
     Master ms = magicGetTheMasterFromRMI();
     new Slave(ms);
}

...

class Slave {

   public Slave(Master m) {
       m.registerSlave(this); // PROBLEM LINE   
   }

}

Problem: The line marked PROBLEM LINE above doesn't work, because I cannot simply pass this (the Slave itself is a Remote which Master will talk back to). I have to explicitly do a UnicastRemoteObject.exportObject(this, 0) (or toStub(this) if it's exported earlier) but that makes the Slave class depend on RMI, breaking the design.

Additionally, registerSlave forces me to catch RemoteException, which also adds RMI dependency.

What would you suggest to solve these issues?

(It also bugs me that these classes must implement Remote, but I guess we can only go so far with abstraction)

A: 

I'd be wary of this abstraction - remotely served requests are different from locally served requests in many ways - latency, failure modes, retry semantics.

It could be that your abstraction is leaky because its not really valid.

Visage
Thanks for your comment, and I'll keep it in mind. I personally feel a leaky abstraction is better than no abstraction, as long as you know about the leakyness.
Bart van Heukelom
+1  A: 

Well, I've done it like this:

interface Service {
   void doIt();
}

class ServiceImpl implements Service {
  public void doIt() { ... }
}

interface RemoteService extends Remote {
  void proxyDoIt() throws RemoteException;
}

class RemoteServiceHost implements RemoteService {
  public void proxyDoIt() throws RemoteException {
    // serviceImpl is a field, defined in the constructor
    serviceImpl.doIt();
  }
}

class RemoteServiceClient implements Service {
  public void doIt() {
   try {
    // remoteService is a field, defined in the constructor
    remoteService.proxyDoIt();
   } catch (RemoteException e) {
    throw new RuntimeException(e);
   }
  }
}
Bart van Heukelom
A: 

Some part of your Slave application is going to have to be prepared to receive call via RMI, and deal with RemoteException, etc. Why not introduce a proxy of some sort between the Slave and Master that mediates the communication and hides the RMI vs local issue. Eg, along the lines of:

public Slave(Master m)
{
   new MasterConnection(m, this);
}

class MasterConnection implements Slave extends UnicastRemoteObject
{
   Slave s;

   public MasterConnection(Master m, Slave s) throws IOException
   {
      this.slave = s;
      try {
         exportObject(this);
      } catch (RemoteException e){
         throw new IOException("Could not communicate with Master etc....");
      }
      master.registerSlave(this);
   }

   public void callbackFromMaster(Object arg) // or whatever
   {
      s.callbackFromMaster(arg);
   }
}
mwhidden
+1  A: 

RMI needs explicit exporting of objects

Only if they don't extend UnicastRemoteObject or Activatable. If they do they are auto-exported on construction.

I have to explicitly do a UnicastRemoteObject.exportObject(this, 0)

No, see above.

(or toStub(this) if it's exported earlier)

Whatever toStub() is. There's a RemoteObject.toStub(), but you can't be calling that, and if you are you're wasting your time.

But you don't have to do that at all. If 'this' is an exported remote object you can just pass it around as an RMI parameter or result. RMI will substitute the stub automatically.

EJP
`this` is not an exported object, but the source `Remote` object. I'll look into the first part of your answer though.
Bart van Heukelom
Make up your mind. You said 'if it's exported earlier'.
EJP