Dear community, my name is Francesco Russo and this is my very first post in this discussion group. Some months ago I started out a new web-based project and I decided to adopt GWT as my GUI framework, fascinated by its potential and capabilities. After a few weeks of practicing and tweaking around with GWT I realized there was something missing. What I was looking for was what we might call some sort of "glue code" that had to be interposed between my GUIs and those classes in charge of invoking GWT-RPC services. Furthermore, I felt the need to devise a solution that would have guided developers working on the project in organizing the interaction between GUIs and RPC-services in a well defined manner.
After some days of sketches and tests I've come out with the pattern I would like to introduce you today, hoping to have as much feedback as possible from you all. Here follows the brief article I wrote that introduces the simple idea. Hope you have to time and interest to take a look at it and hope to receive constructive opinions about its contents. Best regards, Francesco Russo --------------------------------------------------------- Decoupling GUIs from Asynchronous Services 1. Abstract Due to the recent proliferation of asynchronous-services based API it is desirable to devise a pattern that helps in building applications leveraging those new APIs in an easy and effective way, still enticing separation of concerns and decoupling amongst the GUIs and those components in charge of interacting with the asynchronous services. APIs such as the Google Web Toolkit framework are a good example of such libraries in use today for building new highly-interactive web applications (the so-called RIAs) where services are handled in an asynchronous way (Google Web Toolkit RPC). The most direct and naive approach when working with such technological frameworks is to directly tie the action that must be executed in response to user-driven events (i.e. the invocation of a remote asynchronous service) to a visual widget (i.e. a button). While simple and easy, this approach does not keep pace with either the technological evolution of the middleware actually used to perform the remote-service invocation or the unpredictable changing of requirements. It is evident that there is a need of decoupling between user-driven event sources and those components in charge of actually interacting with the remote services. This article introduces a simple yet working approach to solve the problem briefly described. The next section better constraints the scenario we have to deal with and defines our main objectives. The third section deals with the basic idioms the pattern is based on giving for each of them a Java-based description, its responsibility and the role played. The fourth section deals with a simple example scenario which is based on the popular GWT framework, and in the fifth section some conclusions are drawn. 2. Scenario, Constraints and Objectives The scenario to deal with can be characterized as follows: ⁃ we have a set of services we can exclusively access asynchronously (i.e. using GWT-RPC, Google Protocol Buffer or JMS): ⁃ we have to write the GUIs that will expose those services to users ⁃ being clever developers we want to decouple GUIs from those components able to invoke the services Our commitment is now entirely focused on devising the better way we can satisfy those requirements. 3. Solution 3.1 The Action We firstly introduce a contract that defines what an action can be: public interface ActionIfc<T> { public void addListener(ActionListenerIfc<T> lsnr); public void perform(); public void removeListener(ActionListenerIfc<T> lsnr); public void setDataProvider(DataProviderIfc provider); } An action is in charge of executing the interaction with the asynchronous service, that is invoking it by passing in some arguments and being notified upon request completion with either a positive or a negative outcome. An action is typed, in a sense that it's definition can strictly define the actual type (T) of the data-structures that represent the asynchronous invocation results (i.e. the return type of the actual service invocation or a transformation of it). An action must be able to notify interested listeners about the actual results produced by the service invocation. 3.2 The ActionListener An action-listener is a component interested in the outcome of the (asynchronous) service invocation and can be defined as follows: public interface ActionListenerIfc<T> { public void outcome(AsynchInvocationResult<T> res); } An action-listener is in charge of deciding how to drive the user- interaction according to the result of the asynchronous invocation. An action-listener might decide whether to report an error message in the view asking the user to try again (maybe altering some input data), or to do whatever the use-case requires. An action-listener is typed in a sense that it can explicitly declare the strict type of data-structures (invocation results) it can deal with. Note that the type T defined here generally matches the one defined in the ActionIfc (see previous sub-section). An action-listener is notified by ActionIfc concrete implementations upon either successful or unsuccessful asynchronous services invocations. 3.3 The DataProvider Since actions might require data collected from the user to actually invoke services, they can define (if needed) a specialization of the following DataProviderIfc that dictates the methods that must be implemented by another component (possibly the view) to provide the action with the required data. public interface DataProviderIfc<V> { public V getData(); public boolean isDataValid(); } The DataProviderIfc declares a type parameter called V that defines the actual type of the data required by the action to perform the service invocation. This type parameter is intentionally called V rather than T to make it clear it is generally different than the type parameter T used to mark the return type of service invocations. The methods a DataProviderIfc must declare are two at least: - the T getData() method returns the actual data that will be used by the ActionIfc implementation to invoke the service - the boolean isDataValid() method states whether the data is actually valid (thus ready to be consumed by the action) or not Note that the latter method is meant to be used by concrete actions to check the data retrieved by means of the former one right before performing the service invocation that would otherwise fail due to the inconsistent input provided. 3.4 The AsynchronousInvocationResult Asynchronous-invocations results are represented by means of AsynchronousInvocationResult instances. public class AsynchInvocationResult<T> { private final boolean ok; private final T payload; public AsynchInvocationResult(boolean ok, T content) { this.ok = ok; this.payload = content; } public boolean isOk() { return ok; } public T getPayload() { return payload; } } This class is designed to represent a service invocation result. As such it defines basically two properties: ⁃ a boolean stating whether the invocation succeeded or not ⁃ a parameterized-type T that declares the concrete type of the real response obtained as a result for the service invocation (note that this type-parameter matches the one defined in the ActionListenerIfc declaration) The action-listener is able to process such a data-structure and react as needed, but it will typically forward it to registered listeners (ActionListenerIfc concrete implementations) that will receive it through their implementation of the outcome(AsynchInvocationResult<T>) method. Note: if necessary this class might be extended to store also an exception generated by the asynchronous-service in case of failures. 4. Example Scenario As an example we take into account a sample user-registration function for a GWT-based web-application. 4.1 The Action Let' start with the action that is in charge of invoking the remote GWT-RPC service, the RegistrationAction: public class RegistrationAction extends AbstractAction<RegistationServiceResult> implements ActionIfc<RegistationServiceResult> { private RegistrationDataProviderIfc provider; public interface RegistrationActionLsnrIfc extends ActionListenerIfc<RegistationServiceResult> {} public interface RegistrationDataProviderIfc extends DataProviderIfc<UserInfo> {} public RegistrationAction(RegistrationActionLsnrIfc lsnr, RegistrationDataProviderIfc provider) { super.addListener(lsnr); this.provider = provider; } public void perform() { if(dataProvider.isDataValid()) { UserInfo userInfo = dataProvider.getData(); // input data seems fine, let's contact and invoke the registration service Object service = GWT.create(RegistrationService.class); RegistrationServiceAsync reg = (RegistrationServiceAsync) GWTServiceLocator.getService(service, GWTServiceLocation.USER_REGISTRATION_SERVICE); // asynch callback setup AsyncCallback asyncCallback = new AsyncCallback() { // ops a failure public void onFailure(Throwable err) { ActionListenerIfc.AsynchInvocationResult<RegistationServiceResult> outcome = new ActionListenerIfc.AsynchInvocationResult<RegistationServiceResult>(false, null); RegistrationAction.this.notifyListeners(outcome); } // seems like it worked out well public void onSuccess(Object res) { RegistationServiceResult regRes = (RegistationServiceResult) res; ActionListenerIfc.AsynchInvocationResult<RegistationServiceResult> outcome = new ActionListenerIfc.AsynchInvocationResult<RegistationServiceResult>(true, regRes); RegistrationAction.this.notifyListeners(outcome); } }; // service invocation reg.registerUser(userInfo, asyncCallback); } else { ActionListenerIfc.AsynchInvocationResult<RegistationServiceResult> outcome = new ActionListenerIfc.AsynchInvocationResult<RegistationServiceResult>(false, null); notifyListeners(outcome); } } } As we can see, this concrete class implements ActionIfc<RegistationServiceResult>. This means this component is an action that will return instances of RegistrationServiceResult (the details of this class are not relevant to the discussion). The above class also declares two inner interfaces: the RegistrationDataProviderIfc and the RegistrationActionLsnrIfc. As we already know, the former enables the action to get access to the data structure required to perform the service invocation, while the latter represents the instance that will have to be notified once the remote service will return a result. The perform() method (declared by the Action<T> interface) firstly checks the service input data for consistency invoking the RegistrationDataProviderIfc.isDataValid() method and only if the data is consistent the service is actually invoked. Thus the service will eventually return either a positive or a negative result, and in either scenarios the action will notify its listener(s) of the service invocation outcome. 4.2 The View The view is represented by a panel that we call the RegistrationPanel in charge of showing four fields to the user that must be used to input the required data: the desired user-name, the password, the confirmed password and her/his email address. public class RegistrationPanel extends VerticalPanel implements RegistrationDataProviderIfc, RegistrationActionLsnrIfc { // omissis: private properties not relevant for the example public RegistrationPanel() { // widgets initializations & setup code } // omissis: code for validating fields content public boolean isDataValid() { return validateUserName() && validatePasswords() && validateEmailAddress(); } public UserInfo getData() { UserInfo userInfo = new UserInfo(); userInfo.setUsername(userName.getText()); userInfo.setPassword(password.getText()) userInfo.setEmail(eMail.getText()); return userInfo; } public void outcome(AsynchInvocationResult<RegistationServiceResult> res) { if (res.isOk()) { // tell the user he/she has been registered into the system } else { // report an error message to the user } } } The RegistrationPanel above implements both the interfaces defined by the RegistationAction concrete class. The outcome() method, which is derived from the RegistrationActionLsnrIfc, is invoked by the action upon completion of the remote service. Both the isDataValid() and the getData() methods are derived from the RegistrationDataProviderIfc instead. As we have already showed they are used by the RegistrationAction to get access to the data required to perform service invocation and to validate them. 5. Conclusions The View Action Model (VAM) pattern is a rearrangement of the well known MVC pattern, one of the most widely adopted patterns ever in the development of both thick and web-based GUIs, especially tailored at asynchronous services interaction. Thus the VAM pattern inherits all the benefits exposed by the MVC pattern and smoothly guide who adopts it in arranging the code in a way that promotes separation of concerns between those components responsible of managing the user-interaction and those ones meant for interacting with the back-end services. This evident separation of responsibilities amongst components also shields views from changes in the underlying technology used to interact with the services, enabling you to drastically change your actions without repercussions on the views. As already mentioned in the abstract nowadays we already have a number of technologies that let us expose and access services asynchronously (GWT-RPC, Google Protocol Buffer and JMS to mention a few), thus this approach can also be profitably applied where such technologies are applied. Another important aspect that must be taken into account is that the adoption of this pattern can foster the use of the well known test by mocking approach. User interfaces (views) can be easily tested against mock- actions that simulate what the real ones will actually do. Francesco Russo --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group. To post to this group, send email to Google-Web-Toolkit@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/Google-Web-Toolkit?hl=en -~----------~----~----~----~------~----~------~--~---