This week's book giveaway is in the Other Open Source APIs forum. We're giving away four copies of Storm Applied and have Sean Allen, Peter Pathirana & Matthew Jankowski on-line! See this thread for details.
A big issue that most of the candidates had found with this thick client is that the DB interface doesn't throw the RemoteException in its methods when we decide to use RMI. After searching approaches in this forum and reading alternatives in other sources I opted for implementing the Proxy pattern to have the DB interface in the client and the server side.
What I did in concrete was to develop a remote adapter interface that has the same methods that the DB interface but I added a RemoteException in each one. The implementation of this remote interface is essentially an wrapper of my Data class. Then I simply register this object in the RMI registry. Of course this is in the server side.
In the client side I created a Proxy class that implements the DB interface and in the constructor I look up the remote adapter object registered in the RMI. I found this approach pretty simple.
Now the "dark" side of this solution is: The proxy class is an wrapper of a remote object but still somehow has to deal with the RemoteExceptions. I don't rememember well but in one thread in this forum I read that someone wrote "and the proxy class eliminates the RemoteExceptions". My mind was blowing because I had learned that I should not swallow these exceptions just because the DB interface doesn't throw them!!!.
Then my solution was to create a NetworkException that extends a RuntimeException and declare it as part of the API of the DBProxy class.
The compiler is happy and so do I. Everything still is simple.
Now, which class is going to use this DBProxy that implements the DB interface?, of course a SubcontractorServices class. This class takes a DB interface in its constructor . Also, this class implements a bookSubcontractor method and a searchSubcontractors method. Then a programmer knows that these two methods depend only of the structure of the DB interface methods.
However, things get glommy when you reason this: what if I pass a DBProxy class in the constructor. This is valid because the DBProxy class implements the DB interface that is what the SubcontractorServices class needs. But what if in the middle of one operation for example, the DBProxy read method throws a NetworkException, how the programmer knows that he has to catch this exception if is not documented in the DB interface.
I as the developer of these classes know that I have to catch this NetworkException(RuntimeException).
The solution is simple again, I just have to catch the NetworkException in a catch that accepts an NetworkException. But again, I am the developer and know that someone could pass either a Data class (standalone mode) or a Proxy class (client mode), and thus I am aware that I have to catch this unchecked NetworkException.
But.....someone that is not the developer will not have a clue that the read method could throw the NetworkException simply because is not documented in the DB interface. If a junior programmer reads the code he may think, where the hell does this NetworkException come from???.
My idea is to simply document in the SubcontractorServices class that a DBProxy class could be passed and this class throws the unchecked NetworkException in each one of its methods.
What do you think, it is enough to document this "ghost" exception in the SubcontractorServices class? The logic of the application is simple at least for me. In the standalone mode you first create your Data class and then pass it to the SubcontractorServices class. In the client mode you first create your DBProxy class and then pass it to the SubcontractorServices class.
Joined: Jun 30, 2013
I just checked Andrew Monkhouse's approach. He did something similar but in the controller class.
Inside the controller he request a specific DBClient implementation depending on the application mode. In his solution he uses a factory instead of passing the DBClient to the constructor of the controller class.
Now the way he deals with the RemoteExceptions in the controller class (that in my case would be the SubcontractorServices class) is to catch them using a general Exception. However, in his case another programer that looked at his code would know why that exception is there, to treat RemoteExceptions. Just because it is declared in the DBClient class as a checked exception using the IOException.
In my project I could use a the same tecnique and have a catch with a general Exception to indicate: "... and any other exception (including the unchecked NetworkException) will be treated here.". Of course I will document it and everyother programmer would be aware what is going on.
After reading this kinda long post, I personally don't recommend using a RuntimeException.
My understanding is that your basic problem is how does the client know which service to call (local or network)? RIght?
I figured you used RMI. At least here is my version when I did it. The remote service implementation delegates to the local service implementation. I have an interface that extends the Oracle provided interface that throws DatabaseException and some other exception I don't remember. So when say a RemoteException or some other like RecordNotFoundException is thrown in the services, I rethrow it as DatabaseException for example and inform the user.
I have an interface that extends the Oracle provided interface that throws DatabaseException
I also have an extended interface to add my custom methods but the original DB interface doesn't throw a DatabaseException. I am only allowed to throw a DuplicateKeyException or a RecordNotFoundException (that extend the DatabaseException).
So if a RemoteException happens I can't rethrow a DatabaseException because is a more general class and is not declared in Oracle's interface. The other alternative was to rethrow one of the allowed exceptions but it would not make sense to rethrow a RecordNotFoundException for example if I get a RemoteException.
What K. Tsang obviously missed (I guess) in your explanation is that you opted for a thick client, so you have to stick with the provided interface (and that's why DBProxy implements DB). He (and me too by the way) opted for a thin client approach. So we created a business service interface (with search and book methods) and signature of the methods were completely up to us to decide. So the RemoteException was not a problem, throwing a checked DatabaseException from these methods also not a problem.
Regarding Andrew's book he was very lucky with the required to implement interface he got (from himself ): all the methods throw an IOException. So handling the RemoteException isn't a problem, because it's a subclass from IOException. And I'm pretty sure that's done by purpose, otherwise someone could copy/paste his source code, alter some variable names, submit and pass this certification. So now you (as a reader and potential certified java developer) have to do quite a lot of thinking. I think Andrew did a great job in writing a book that covers all aspects of this certification, but you can only use the concepts described in the book, you can't copy/paste the code from the book for your own assignment. I guess that was the very tricky part of writing this book.
I think your problem isn't a really big issue and can be easily solved. I would simply add a javadoc comment to the SubcontractorServicesImpl class (not the interface). Something like: "Important note: This class can be used to perform both local and network calls. When a network call fails a NetworkException will be thrown, so don't forget to handle it appropriately!". Any other programmer has to read and use the API correctly when developing some new features or changing existing ones. And in your choices.txt you simply add an explanation why you introduced this NetworkException, why it must be a RuntimeException (because you can't change the required interface, it's a violation of a must requirement) and you are completely ready for submission!
Well, ..... done!!! I needed your approval. Yes I have a SubcontractorServices interface and a SubcontractorServicesImpl that is the implementation class. I hope that someone else that read this thread doesn't get confused when I said that SubcontractorServices was a class.
Thanks again K. Tsang and Roel for your help!! (Next friday is my deadline, so just one more week ..... just one more week.... )