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
-~----------~----~----~----~------~----~------~--~---

Reply via email to