Wednesday, March 5, 2014

Presentation Layer Under Control

Hower there is bunch of patterns addressing presentation layer issues, implementing clean presentation layer in Java desktop application is tricky task. Main problem for me is a lot of mess inside. I'd like to present my approach to take control of presentation layer, which is combination of existing ideas such: Supervising Controller, Passive View, Presentation Model, Publish-Subscribe, Observer.

This will be an approach, not a pattern. The reason is a pattern is something widely used and this will be just set of tips. So you won't be able to apply it by just repetition some steps, but by interpretation the approach in the current context and SOLID analysis. But if you are not familiar with patterns listed above and presentation layer issues of desktop application, try Mediator pattern or SmartUI approach first. You will have some mess in the code but still under control.

So, if you want to take a look on risky implementation of a presentation layer, check out this example. As far I remember some folks call this SmartUI anti-pattern.

Well, the UI is really smart in the example. It contains child UI components, implements listeners and does both UI and business processing. This GUI is simple but continuing this approach leads to code which is hard to understand, hard to test, hard to develop.

The idea

Let's list challenges for the desktop applications with visual presentation layer:
  • communication between controls
  • managing application state - there are three types of it: screen state, session state and record state
  • type conversion between view and rest of the application
  • handling user events
  • keeping GUI responsive - typically with threads
  • testability - Humble Dialog aka MVP is a solution

Take look on the figure.

In the typical application all controls are grouped by their business purpose and UX rules. Let's treat the controls arrangement as being not so important for the application architect. What is important that we have many groups of controls, each group has its business purpose and groups need to communicate with others. Let's call these groups components.

However components might be visually arranged in a hierarchy (depends on UX designer), from the source code side they are totally equivalent. Component to communicate with other component use an event bus. When an event comes to a component it updates its state (what mostly means a user view is changed) according to the subscribed event.

Using event bus gives all benefits of the Publish-Subscribe pattern. You may use topics or even two or more event buses for really complex GUIs.

This was an idea. Now let's take a look how a component is designed inside.

Structure of a component

Five parts works together as a component.
partresponsibilityout of responsibility
Form * is coupled with a particular GUI technology
* builds itself
* has private helper methods to working with embedded controls
* calculates a control arrangement
* business processing
* calling calling services
* validation
event publishing
FormEventHandler * implements all technology-specific event listeners
* might be decomposed into specialized handlers e.g. DragAndDropHandler
* subscribing events other than technology-specific
* business processing
* calling calling services
* validation
* event publishing
View * exposes methods for getting and Form state (see Nice Move with ValueSetter)
* typically is implemented by Form classes
* event publishing
* event subscribing
ViewObserver * notifies that something happened on a Form
* typically is implemented by the Presenter class
* event subscribing
Presenter * calls business services
* publishes events addressed other components
* subscribes on events from other components
* business processing (except of very rarely cases)

So how you see, a component talks to other components via event bus, but asynchronous communication inside the component is implemented via Observer Patterns. There is nothing wrong with using event bus inside the component also. But in my opinion the solution with event bus outside- and Observer inside- is more clear and easier to test.

and how it works?

Here you have a diagram how a user request is processed by a component.

and also how components talk to others:

ViewModel - what is for?

The problem is how to deal with screen state (keeping Form as stupid as possible), especially when you work with the remote backend and you need to synchronize the client with the server.

Possible solution to this is introducing a session state which is in-memory copy of screen state. Presentation Model pattern do that. The pattern represent the state and behavior of the presentation independently of the GUI controls used in the user interface. It is possible to find Presentation Model structure in this approach. These are: View interface and Presenter.viewModel property.

However implementing Presentation Model is not the goal, I have borrowed viewModel part from the pattern. Why?

In regular MVP pattern View exposes methods to set and get state of Form controls. It's something like that:
public class DetailsPresenter implements DetailsViewObserver {

  private DetailsView view;

  public void handleRequest() {
    String name = view.getName();
    String name = view.getTitle();
    Date dayOfBirth = toDate( view.getDayOfBirth());

    //call services

    view.setTitle( newTitle );
    
    //...
  }
}
This approach causes lots of set/get methods in the View interface. Because the fact that some controls states are needed to business processing the Presenter handler methods are full set/get/add invocations.

An alternative to this is full-featured Data Binding. When something changes on the screen it is automatically reflected to the viewModel and vice versa. Really magic Data Binding means lots of listeners, controllers and events going there and back again.

So, what instead? Map? Well, map might be a bag for everything, but idea is good. I use the apache-commons DynaBean as viewModel implementation. ViewObserver exposes the method to notify about changes of screen state.
public class DetailsPresenter implements DetailsViewObserver {

  private DynaBean viewModel;
 
  private DetailsView view;
 
  public void buildViewModel() {
    viewModel = new LazyDynaBean();
    viewModel.set("name", "Jack");
    viewModel.set("title", "Dr.");
    viewModel.set( "dayOfBirth", new Date());
  }

  public void handleRequest() {
    String name = viewModel.get("name");
    String name = viewModel.get("title);
    Date dayOfBirth = viewModel.get("dayOfBirth");

    //call services

    view.updateControl( "title", viewModel.get( newTitle) );
    //or updateAllControls()
  }

  @Override
  public void propertyChanged(String property, Object value) {
    viewModel.set(property, value);
  }
}

public class DetailsFormListener implements DocumentListener {
  
  private DetailsViewObserver viewObserver;

  public void changedUpdate(DocumentEvent e) {
    viewObserver.propertyChanged("title", e.getDocument().getText());
  }

  //...
}

Implementation details

You may fetch examples from my Github profile.

pl.bnsit.architecture package

This is basic template of the approach.

pl.bnsit.flights package

This is an example with coule windows, validation and so on. But I followed the typical llayered architecture.

pl.bnsit.onepieceflow package

There I went a step ahead. I tried to write simple Kanban board. But I started thinking iin a component-like way. I asked myself What are independent components on this GUI? and answered: a card, lane containing set of cards and board containing set of lanes. Then I realized that the independency might be considered through all application layers, not only inside the presentation layer.

Continuing this way of thinking we come the architecture with small, well-defined components anchored in the business domain, which are working together to do a business task.

Type conversion

Notice that some type conversion must be done between viewModel and Form. I did this with TypeConverter interface:
public interface TypeConverter {
  String toString( Object anObject );
  Object fromString( String str);
  Class type();
}
Conversion is done when the control state is updated:
public class AddingCustomerForm extends JFrame implements AddingCustomerView {
 
  private Map converters;

  public void updateComponent(String id, Object value) {
    Object convertedValue = convert(value);
    JComponent component = components.get(id);
    ValueSetter.get(component.getClass()).set(component, converterValue);
  
  }

  private Object convert(Object value) {
    Object converterValue = value;
    
    if( converters.containsKey(value.getClass()) ) {
      TypeConverter converter = converters.get(value.getClass());          
      converterValue = converter.toString(value);
    }

    return converterValue;
  }

and back, when Listener notifies via ViewObserver that control state was changed.

No comments:

Post a Comment