Errai: The browser as a platform

Tuesday, March 6, 2012

Large scale application development with GWT and Errai

Development teams collaborating on the same codebase are always faced with the challenge of keeping their source code maintainable as it evolves and grows over time. By choosing GWT to implement your web application you already gain the benefit of having IDE support for client-side code refactorings. This is a distinct advantage, but how should a GWT application be designed to allow for the collaboration of large teams and stay maintainable over the course of time?

The Model-View-Presenter (MVP) pattern provides an answer to this. Applying the MVP pattern in GWT applications is described in this two-part article: Large scale application development and MVP

In this post, I will demonstrate a modified version of the example code of this article series to show how Errai makes implementing a MVP application in GWT even easier and how Errai helps to further decouple the individual components. The source code of the modified example can be found on GitHub: https://github.com/csadilek/errai-mvp-demo

So, let's take a look at the individual components used in MVP and the modifications and improvements made to it using Errai.

The Model

The business objects used in this example (Contact and ContactDetail) remain more or less unchanged. The @Portable annotation was added to mark them as serializable by Errai. Errai marshalling was designed for maximum flexibility and to impose as few limitations to serializable classes as possible (see our documentation for details). The classes do not need to (but may of course) implement java.io.Serializable.

The View

The views contain only UI layout code. They have no knowledge of the model, other views, transitions and contain no application logic. Errai can improve view implementations by providing the ability to inject UiBinders.

 Without Errai:


public class EditContactView extends Composite implements EditContactPresenter.Display {  

  interface EditContactViewUiBinder extends UiBinder<Panel, EditContactView> {}
  private static EditContactViewUiBinder uiBinder =  
      GWT.create(EditContactViewUiBinder.class);

  ...
}

With Errai:


public class EditContactView extends Composite implements EditContactPresenter.Display {

  @Inject 
  UiBinder<Panel, EditContactView> uiBinder;

  ...
}

The Presenter

The presenters contain all the application logic: view transitions, RPCs, etc. In the original example, the RPC proxies, the event bus and the view instances are passed to the presenters as constructor arguments. Using Errai, we can now inject all these components.


@Dependent
public class EditContactPresenter implements Presenter {
  public interface Display {
    ...
  }

  @Inject
  private Caller<ContactsService> contactsService;

  @Inject
  private HandlerManager eventBus;

  @Inject
  private Display display;

  ...
}

This might seem like a minor improvement at first, but once your application grows this will become more than handy. Let's say you come to a point where all your presenters need an additional RPC proxy. Without dependency injection, you would have to change all constructors and all call sites!

The GWT/RPC proxy was replaced with an Errai RPC proxy (the injected Caller). Errai RPC does not require an asynchronous interface nor a servlet based service implementation on the server. It can even be used to invoke methods on a server-side CDI bean (see our documentation for details).

The Application Controller

The application controller handles logic that is not specific to any presenter and also deals with history management and view transitions. It needs access to the event bus (which again we can inject using Errai IOC) to react on application events that trigger view transitions.


@ApplicationScoped
public class AppController implements Presenter, ValueChangeHandler<String> {
  @Inject
  private HandlerManager eventBus;
  
  @Inject
  private IOCBeanManager manager;

  ...
}

It also needs to be able to create new presenter instances when the browser's history stack changes. To create new managed instances of presenters, we use Errai's client-side bean manager that will make sure all the injection points of presenters are satisfied and the instance is ready to use.


public void onValueChange(ValueChangeEvent<String> event) {
    String token = event.getValue();
    if (token != null) {
      Presenter presenter = null;

      if (token.equals("list")) {
        IOCBeanDef<ContactsPresenter> bean = manager.lookupBean(ContactsPresenter.class);
        if (bean != null) {
          presenter = bean.getInstance();
        }
      } else if (token.equals("add") || token.equals("edit")) {
        IOCBeanDef<EditContactPresenter> bean = 
            manager.lookupBean(EditContactPresenter.class);
        if (bean != null) {
          presenter = bean.getInstance();
        }
      }

      if (presenter != null) {
        presenter.go(container);
      }
    }
  }

The EntryPoint

Since Errai allows us to use dependency injection for all the required components, the main difference here is that we no longer have to pass the RPC proxies and the event bus instance to the app controller which in turn would have to forward it to the presenters and views.

The @Produces method provides the event bus instance to all the event bus injection points in all the presenters. The application controller itself is also just injected and started as soon as the app is finished initializing (@AfterInitialization).


@EntryPoint
public class Contacts {

  private HandlerManager eventBus = new HandlerManager(null);

  @Inject
  private AppController appController;

  @AfterInitialization
  public void startApp() {
    appController.go(RootPanel.get());
  }

  @Produces
  private HandlerManager produceEventBus() {
    return eventBus;
  }
}

So to sum it up, Errai's IOC container helps implementing the MVP pattern by further decoupling the individual components and by alleviating boilerplate code for UiBinders and RPC proxies.

Future Work 

An extension that we are currently working on is the @MockFor annotation that can be used to replace an injected type with a mocked type for unit testing. During tests, a presenter could then get the mocked view injected in place for the actual view for example. The presenters can then be tested using a plain JUnit test instead of an integration test.

@MockFor(EditContactsView.class)
public class EditContactsMockView implements EditContactPresenter.Display { 
  ...
}

As always, feedback is welcome and appreciated!

8 comments:

  1. Thank you for the sample. I plan to use it as a model for the current project I am working on.

    ReplyDelete
    Replies
    1. You're welcome! Be sure to stop by our project forum or IRC channel in case of questions or suggestions for improvement.

      Delete
  2. I am having trouble compiling code derived from the template you describe here. I have:
    @Dependent
    public class ViewPresenter implements Presenter {

    public interface Display {

    Widget asWidget();
    }
    @Inject
    private HandlerManager eventBus;
    }

    and

    @EntryPoint
    public class ForumClient {

    private HandlerManager eventBus = new HandlerManager(null);

    @Inject
    private AppController appController;


    @AfterInitialization
    public void startApp() {
    appController.go(RootPanel.get());
    }

    @Produces
    public HandlerManager produceEventBus() {
    return eventBus;
    }
    }

    but I am getting the following error:
    Rebinding org.jboss.errai.ioc.client.api.Bootstrapper
    Invoking generator org.jboss.errai.ioc.rebind.IOCGenerator
    Generating Extensions Bootstrapper...
    Rebinding org.jboss.errai.ioc.client.api.Bootstrapper
    Invoking generator org.jboss.errai.ioc.rebind.IOCGenerator
    Generating Extensions Bootstrapper...
    [WARN] For the following type(s), generated source was never committed (did you forget to call commit()?)
    [WARN] org.jboss.errai.ioc.client.api.BootstrapperImpl
    [ERROR] Errors in 'jar:file:/C:/Users/oli/.m2/repository/org/jboss/errai/errai-ioc/2.0.Beta3/errai-ioc-2.0.Beta3.jar!/org/jboss/errai/ioc/client/Container.java'
    [ERROR] Line 39: Rebind result 'org.jboss.errai.ioc.client.api.BootstrapperImpl' could not be found
    Scanning for additional dependencies: jar:file:/C:/Users/oli/.m2/repository/org/jboss/errai/errai-marshalling/2.0.Beta3/errai-marshalling-2.0.Beta3.jar!/org/jboss/errai/marshalling/client/api/MarshallerFramework.java
    Computing all possible rebind results for 'org.jboss.errai.marshalling.client.api.MarshallerFactory'
    Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
    Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
    Generating Marshallers Bootstrapper...
    Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
    Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
    Generating Marshallers Bootstrapper...
    Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
    Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
    Generating Marshallers Bootstrapper...
    Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
    Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
    Generating Marshallers Bootstrapper...
    Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
    Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
    Generating Marshallers Bootstrapper...
    Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
    Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
    Generating Marshallers Bootstrapper...
    [ERROR] Cannot proceed due to previous errors
    ------------------------------------------------------------------------
    BUILD FAILURE
    ------------------------------------------------------------------------

    ReplyDelete
    Replies
    1. Please use the forum or our IRC channel. We can help with troubleshooting there. Also check out the code for this example at https://github.com/csadilek/errai-mvp-demo . It compiles/runs fine.

      Delete
  3. Will the application controller handle code splitting if used this way?

    ReplyDelete
  4. Why you use @Dependent scope for presenters?

    This way presenter is created every time History.newItem is invoked. But where is garbage collector? You never remove old preseters. And this is good place for memory leaking.

    ReplyDelete
  5. If you are looking for a Software Development Company in Delhi you can check this site http://www.acetechindia.com/

    ReplyDelete