Building An Advanced Navigation for Enterprise Applications

Nowadays the navigation system for enterprise application is not a very clear topic. Quite often a user has to dig deep into a lot of documentation, trying to figure out and understand the logic of collaborations of screens. Navigation through the application quite often is a huge set of tools where you had to learn what tool in what order to apply. In general, the entire logic of navigation through the application the user has to keep in mind. 

However, would be great to be able to use the system as easy as for instance the internet browser. Navigate to the particular page with one or two clicks; be able to see a trail of visited pages within the application and to have a clear and simple mechanism for the entire application.

This topic tries to shade the light into the issue and to look deeper in one of the possible implementations of navigation within the application, step by step, describing the solved problems and what results it has led to.

Introduction

Every enterprise application has its own domain model where connections between different system entities are definitely determined. Entities can be related to each other with different relationships: one-to-one, one-to-many, many-to-many.

Suppose we have some domain model:

Different views can reference every entity, for instance CRUD, reports, graphics, and lists within a table. Also views can display several entities at once, for instance as it is shown on the picture below where B-view works with entities of classes B, D and E.

 According to the references on the level of domain model user can move between the corresponding views, for instance as it is shown on the picture blow starting from view  B to move to view C, then to F and then to E. 

Next time the user can start working from the A view and then by the chain to move on until the E view. 

Or one more case, the user could start working from the other side, from the E view and by the transitions to reach the A view.

To implement the variety of different navigations via the application first we need to select the technologies.

 MVC or a component framework

 Choosing the web framework for the next project it could be great to combine the advantages of desktop application development based on component frameworks with the benefits of MVC frameworks for building web applications. To take from component frameworks the simplicity of creation of different forms, tables, graphics and other elements of interface and join it with the possibility to manage navigation via the application with the help of configuration similar to how it is implemented in Struts and Spring Web Flow.

As a component framework we have selected Vaadin but we had not found the appropriate implementation of navigation similar to Spring Web Flow. Attempt to integrate Vaadin and Spring Web Flow was failed due to the significant differences in mechanism of Request/Response model.  That’s why we decided to implement our own Web Flow solution supposed to be independent from the Request/Response model.

As a basis we have taken UML Statechart  and have implemented Lexaden Web Flow. Then we created a communication mechanism between the Statechart and the component model so that it was possible to switch visual components depending on the states in a certain area of the application. 

The figure below shows the main components of Lexaden Web Flow: 

Event Processor

Event Processor – this is the processor of events used by the system to send them. Also Event Processor used for the access to meta-information that determines what events are available and in what state navigation process appears to be.

Lexaden Web Flow engine 

Engine reacts on the evens coming from the event processor and performs transition process between states according to the data taken from the configuration before. 

State Controller

State controller reacts on the events coming from Lexaden Web Flow engine and injects views build by controllers into application layout. Also it is responsible for transferring events about transition from one state to another to controllers. 

The beginning

To get a first experience with Lexaden Web Flow, you can take two states and event and switch between application panels.

Example, how it is implemented in XML:


    <flow initial="Panel1" ...> 
       <controller id="Panel1">
            <on event="panel2" to="Panel2"/>
       </controller>
       <controller id="Panel2">
            <on event="panel1" to="Panel1"/>
            <on event="ok" to="OK"/>
       </controller>
       <final id="OK"/>
    </flow>
     

The application switches from controller "Panel1" by means of the event and opens up "Panel2" and vice a versa.  In case of the “ok” event the application execution ends up. 

Further on let’s have a look into the more extended sample. We’ll take a “person” domain entity and will implement CRUD operations in such a way that every operation would be corresponding to a separate state and be managed by its own controller. 


 <flow  initial="list" ...>
     <module id="person">
        <controller id="list">
            <on event="create" to="create"/>
            <on event="read" to="read"/>
            <on event="update" to="update"/>
            <on event="delete" to="delete"/>
        </controller>
 
        <controller id="create">
             ...
        </controller>
 
        <controller id="read">
             ...
        </controller>
 
        <controller id="update">
             <on event="updated" to="list"/>
             <on event="canceled" to="list"/>
        </controller>
 
        <controller id="delete">
             ...
        </controller>
    </module>
 </flow>

But here we have a small issue. How should we come back from "create", "read", "update" and "delete" controllers back to the "list" controller? The most obvious is to create an explicit transition to the "list" controller:


<on event="updated" to="list"/>
<on event="canceled" to="list"/>

 But such variant would tie up "create", "read", "update" and "delete" controllers with the “list” controller, that would not let to reuse them, for instance, from the "read" controller. 

Why do we have to go from “read” controller and back to the “list” controller in order to reach the “update” controller if we can go directly from “read” to the “update” controller?

 The solution is to add a “final” state inside every controller  but not to the external controllers:


<controller id="create">
    <on event="created" to="created"/>
    <on event="canceled" to="canceled"/>

    <final id="created"/>
    <final id="canceled"/>
</controller>
 
<controller id="update">
    <on event="updated" to="updated"/>
    <on event="canceled" to="canceled"/>

    <final id="updated"/>
    <final id="canceled"/>
</controller>
 

During the transition to the final state Lexaden Web Flow is reported that the controller stops working and LWF transfers control to the previous controller with a message composed of the name of the current controller and the name of the final state. For example, "update.updated", "update.canceled" or "create.created" will be used as the result of the work of the controller finished its execution.

Base Controller

Navigating from a controller to a controller we have to re-create a view all the time and to avoid the situation when we have to create a table and a form again and again while returning from controller we have created a special base controller which is inherited by the all controllers. It is responsible for life-cycle management of controller. 

The base controller consists "action" and "view" states. Action states are responsible for the submission initialization and receiving data from the domain model. The view state is designed to determine the time when you need to display a view in the application. 


<controller id="controller" initial="initView">

    <!-- init view. create all components of the view. get ready to setup data  -->
    <action id="initView" extends="action">
        <on to="loadData"/>
    </action>

    <!-- setup data before displaying the view. get data from context attributes -->
    <action id="loadData" extends="action">
        <on to="displayView"/>
    </action>

    <view id="displayView" extends="view">
        <on event="ok" to="ok"/>
        <on event="close" to="close"/>
        <on event="cancel" to="canceled"/>
    </view>

    <final id="close" extends="action"/>
    <final id="canceled" extends="action"/>
    <final id="ok" extends="action"/>

</controller>

The "initView" action is used to initialize the view within the controller. After the controller creates the view then the transition goes to the "loadData" action that is used to load data into the view. 

The "displayView" state indicates the framework to inject the view taken from the controller into the application layout.

On the events: "ok", "close" or "cancel" the controller from the "displayView" state transits the execution into the corresponding final state. It means that the controller has finished its work.

In order to bind the “action” state methods with the corresponding Java controller the following annotations are used:


  @OnEnterState(EventConstants.INIT_VIEW)
  public void initView(StateEvent event) {
      ...
  }

In this case during transition from the “initView” action state the “initView” method is called that is marked by the “OnEnterState” annotation. And the event thrown by the other controller is passed to the “initView” method as a parameter.  

An event can contain any data, such as ID of the selected object in the previous screen and used to load associated data. 

The same way you can subscribe to events when the application goes into the "view" state.


  @OnEnterState(EventConstants.DISPLAY_VIEW)
  public void refreshTable(StateEvent event) {
     …
  }	

This can be used to update data in a table or form each time the user returns to the "displayView" state.

Do you have a lot of controllers? – Group them into modules!

Since enterprise applications can have a lot of controllers it makes sense to combine them into modules and make them independent from each other. Also make them exchange information only by means of events.  For instance from the “orders” module you can come to the “addresses” module by the "go_addresses" event. 

So for instance, set of controllers for CRUD operations can be combined into a module by the following XML representation:


<module id="addresses" initial="list">
   ... 
   <controller id="list" … >
         <on event="go_create" to="create"/>
         <on event="go_read" to="read"/>
         <on event="go_update" to="update"/>
         <on event="go_delete" to="delete"/>
   </controller>
   <controller id="create" … >
        …
   </controller>
   <controller id="read" … >
         …
   </controller>
   <controller id="update" … >
         …
   </controller>
   <controller id="delete" … >
         …
   </controller>
   ...
</module>

Entering the “addresses” module the application goes to the “list” controller. Lexaden Web Flow controllers are tied up to corresponding Java controllers, for instance, it could be done by the following code sample: 


    flowControllerContext.bindController("addresses/list", “content”, new AddressesListController ());
    flowControllerContext.bindController("addresses/create", “content”, new AddressesCreateController ());
    flowControllerContext.bindController("addresses/read", “content”, new AddressesReadController ());
    flowControllerContext.bindController("addresses/update", “content”, new AddressesUpdateController ());
    flowControllerContext.bindController("addresses/delete", “content”, new AddressesDeleteController ());

The AddressesListController controller is associated with a particular view (AddressesListView) that becomes active when the application goes to the "addresses / list" state. The same way other controllers bind their views to "create", "read", "update", "delete" states.  From the controller tied up to the “list” state the application moves to the states "create", "read", "update" or "delete" by the corresponding events and displays appropriate views to the user.

CRUD: main operations with domain objects

Since the most of domain system objects can support the same operations such as List, Create, Update, Delete it would be great to specify navigation logic in a separate “crud” module. After that it could be inherited by other modules in order to create modules for different domain objects with automatic support of CRUD operations.


  <module id="crud" initial="list" ...>

      <controller id="list" extends="controller">
          <on event="create" to="create"/>
          <on event="read" to="read"/>
          <on event="update" to="update"/>
          <on event="delete" to="delete"/>        
      </controller>

      <controller id="create" extends="controller">
          …
      </controller>

      <controller id="read" extends="controller">
          …
      </controller>

      <controller id="update" extends="controller">
          …
      </controller>

      <controller id="delete" extends="controller">
          …
      </controller>

  </module>

  <module id="orders" extends="crud" >
        <on event="go_account" to="account"/>
        <on event="go_addresses" to="addresses"/>
  </module>

  <module id="account" extends="crud"/>

  <module id="addresses" extends="crud"/>


Modules “orders”, “account”, “addresses” inherit the “crud” module specified above and get a copy of transition logic between CRUD states. Now creation of new modules are very laconic that allows easily adding new modules during the development of the systems. 

Readonly + CRUD: delineation of rights to view and edit

To be able to restrict access to application specific operations on domain objects the “crud” module can be divided into two modules - "readonly" and "crud". In this case, the "readonly" module will only be used for reading, and the "crud" module will be used for full editing application entities.


  <module id="readonly" initial="list" extends="module">

      <controller id="list" extends="controller">
          <on event="read" to="read"/>
      </controller>

      <controller id="read" extends="controller"/>

  </module>

  <module id="crud" initial="list" extends="readonly">
 
      <controller id="list" extends="readonly/list">
         
          <on event="create" to="create"/>
          <on event="update" to="update"/>
          <on event="delete" to="delete"/>
            ...
      </controller>
      <controller id="create" extends="controller">
           …
      </controller>

      <controller id="read" extends="controller">
           …
      </controller>

      <controller id="update" extends="controller">
           …
      </controller>

      <controller id="delete" extends="controller">
           …
      </controller>
 
   </module>
 
   <module id="orders" extends="readonly" >
          <on event="go_account" to="account"/>
          <on event="go_addresses" to="addresses"/>
   </module>
 
   <module id="account" extends="crud"/>
 
   <module id="addresses" extends="crud"/>

Since the “orders” module inherits the “readonly” module users are able to see the list of their orders and can look through each module individually but they are not be able to create, update delete them.  Modules “account” and “addresses” inheriting “crud” will let users to create, update and delete entities as well.

Pickers: Modules reuse for picking values from the list

A number of different tables with different settings can be used in application. They used to be bound to “list” states from CRUD modules. But their configuration is set in such a way that they come to “read” controller by the “read” event.  Using inheritance you can reuse not only tables for viewing list of objects but also for value selection from the table. For that purpose you can add a “picker” controller into the “crud” module and will make it inherited from “crud/list” controller.


  <module id="crud" initial="list"...>
      …
      <controller id="picker" extends="crud/list">
          <on event="read" to="picked"/>
          <final id="picked" extends="action"/>
      </controller>
      
  </module>

The "picker" controller inherits the "crud/list" controller and overrides the "read" event, redirecting to the final state - "picked". This allows coming back to the previous screen by clicking the row in the table. Further the previous screen will receive the “picked.picked” event with the identifier of the picked object.  It could be caught in the controller of the previous screen. Then it can refresh the content, for instance, for a dropdown.

Further you need to specify events in modules in order to user this mechanism:


  <module id="orders" extends="readonly" >
        <on event="go_account" to="account"/>
        <on event="go_addresses" to="addresses"/>
        
        <on event="select.account" to="account/picker"/>
        <on event="select.addresses" to="addresses/picker"/>
  </module>

  <module id="account" extends="crud"/>

  <module id="addresses" extends="crud"/>

Transitions go to "account/picker" and  "addresses/picker" controllers by the corresponding "select.account" and "select.addresses" events which allows letting pick values from tables.

The mechanism of  pickers let us not only reuse tables for picking values but also to create, update and delete entities from the lists at our convenience using available CRUD possibilities.

Take modules under your control using Profiles

Since an enterprise system can consist dozens or hundreds of different domain objects there can be a lot of navigation modules. Different combinations of modules can be used by different roles like (administrators, managers or other company employees), that’s why they are combined into profiles, for instance the navigation samples from the article introduction could be grouped in profiles: 

Profile – is a part of the application connected to a particular role within the system. For instance, the administrator can have access to the all functionality of the system when the customer only has access to the limited functionality, for instance to create order; view the status of the order, etc.

Below is an example of a simple profile with configured modules:


 <profile id="customer" ...>
        …
      <module id="orders"  … >
            <on event="go_account" to="account"/>
            <on event="go_addresses" to="addresses"/>
      </module>
      <module id="account" … >
      </module>
      <module id="addresses" … >
      </module>
      …
 </profile>

It is better to specify transitions between modules on the level of profiles to keep modules independent from each other in order to reuse them in other profiles.

To be able to use the same modules in different profiles at the same time Lexaden Web Flow supports inheritance. For instance, specified on a higher level the “t_addresses” module is possible to include in different profiles using inheritance. 


  <flow>
      ...
      <profile id="t_admin" ...>
          …
           <module id="addresses" extends="t_addresses">
                 <controller id="list" extends="addresses/list">
                        <on event="go_export" to="export"/>
                 </controller>
                 <controller id="export" >
                        ...
                 </controller>
           </module>
          …
      </profile>
   
      <profile id="t_customer" ...>
          …
           <module id="addresses" extends="t_addresses"/>
          …
      </profile>
   
      <module id="t_addresses" initial="list">
         ...
      </module>
      ...
  </flow>

The “t_admin” profile and the “t_customer” profile get a copy of the “t_addresses” module specified on the top level of the configuration. Also the “t_admin” module with the help of inheritance broadens possibilities of the “list” controller by adding the “go_export” event. By the event the application will go to the "admin/addresses/export" state. The corresponding view will be bound to the "addresses/export" controller. In such case we have state inheritance with polymorphism that allows selectively change the behavior and structure of modules taking a state template as a basis.

The profiles also could be included into the “application” state with the help of inheritance.


<application id="application" ...>
        … 
        <profile id="admin"  extends="t_admin"/>
        <profile id="manager"   extends="t_manager" />
        <profile id="team_leader"   extends="t_manager" >
              <on event="go_team" to="team"/>
              <module id="team"…> … </module>
         </profile>
        <profile id="employee" extends="t_employee"/>
        <profile id="customer"  extends="t_customer"/>
        …
</application>

<profile id="t_admin" ...> ... </profile>
<profile id="t_manager" ...>... </profile>
<profile id="t_employee" ...>... </profile>
<profile id="t_customer" ...>... </profile>

All the states in the system support inheritance and polymorphism it allows flexibly reusing states making only small corrections.

The main "application" state is used to bind an application layout view that creates basic structure of user interface. It is done the following way: 


flowControllerContext.bindController("application", new ApplicationLayoutController());

Using a “flowControllerContext”  external context the controller “ApplicationLayoutController”  is bound to the “application” state. The layout view contains internal structure of the user interface. The “placeholders” are created inside this structure. The main purpose of which is slicing the layout of the application into different parts where different views will be injected during navigation through the application.

For instance the Left SideBar placeholder can serve as a left menu, the Header placeholder could be used for a logo, search field, login button. The Right SideBar placeholder can serve for various other helpful windows – context sensitive help, property editors and etc.

The Content placeholder serves as a place for the main application information. 

While binding controllers to a particular state the appropriate placeholder should be associated for controllers. 


    flowControllerContext.bindController("addresses/list", “content”, new AddressesListController ());
    flowControllerContext.bindController("addresses/create", “content”, new AddressesCreateController ());
    flowControllerContext.bindController("addresses/read", “content”, new AddressesReadController ());
    flowControllerContext.bindController("addresses/update", “content”, new AddressesUpdateController ());
    flowControllerContext.bindController("addresses/delete", “content”, new AddressesDeleteController ());

All controllers are bound to the “addresses” module will be bound to the “content” placeholder. Views of those controllers will be displayed in the corresponding place of application layout during the program execution process.

Flows: execution process

To be able to implement navigation Lexaden Web Flow uses mechanism of flows – execution processes. In order to start a flow you need to specify the type="flow” attribute for the “on” tag. It can be done at the level of profile definitions. When the application throws an event with the "flow" type, Lexaden Web Flow either starts a new flow or switches to the already active flow. 


  <profile id="manager"   … />
     <on type="flow" event="orders.flow" to="orders"/>
     <on type="flow" event="account.flow" to="account"/>
     <on type="flow" event="addresses.flow" to="addresses"/>

     <module id="orders" extends="readonly" >
           <on event="go_account" to="account"/>
           <on event="go_addresses" to="addresses"/>
     </module>

     <module id="account" extends="crud"/>

     <module id="addresses" extends="crud"/>
  </profile>

The application menu is used to send events with the “flow” types. It opens up a flow execution in the tabs, as shown in the figure below: 

The application flows are bound to the closable tabs. The navigation path is indicated by the Breadcrumb component within the active flow. 

Controllers work as asynchronous functions. To finish up their work they are supposed to be entering into several internal final states of type “final”. Entering this state, Lexaden Web Flow creates a new event, consisting of the name of the controller and the name of the final state within the controller. For example, for the "read" controller with the "ok" internal final state, LWF will generate a "read.ok" event, throwing the event caught up by the previous flow controller. This allows processing the execution results by it. 

  <controller id="list" extends="controller">
      <on event="read" to="read"/>
      <on event="read.ok".../>
      ...
  </controller>

  <controller id="read" extends="controller">
      <on event="update" to="update"/>
      <on event="update.updated" to="updated"/>

      <action id="updated" extends="action"/>

      <!-- this part already exists in the "read" controller such as 
          it is inherited from the parent "controller" state
          <view id="displayView" extends="view">
          <on event="ok" to="ok"/>
          ...
          </view>
          <final id="ok" extends="action"/>
      -->
      …
  </controller>

  <controller id="update" extends="controller">
      <on event="updated" to="updated"/>
      <final id="updated" extends="action"/>

      <!-- this part already exists in the "update" controller such as
          it is inherited from the parent "controller" state
          <view id="displayView" extends="view">
          <on event="cancel" to="canceled"/>
          ...
          </view>
          <final id="canceled" extends="action"/>
      -->
  </controller>

The sequences of transitions between controllers are remembered in flows as well as between corresponding views.

The flow finishes its execution when the last controller enters into one of the final states. In this case the event with the "endFlow" type is thrown. It is processed the following way:

    @OnEvent(type = EventConstants.END_FLOW)
    public void onCloseTab(StateEvent event) {
      ...
    }

And it can be used, for example, to close the tab associated with the flow.

The result work of the application based on Lexaden Web Flow can be watched on the video, starting from the second minute

Possible advantages of this technology:

Can be used as a basis for the Use Cases description in the application

For component frameworks partially solves the problem of memory, allowing you to create pages on demand, instead of loading all screens of application into memory at once.

It may solve the navigation problem for large-scale administration systems for schools, hospitals, banks, etc.

Simplifies system understanding by customers, unifying navigation throughout the application that in its turn can reduce maintenance costs in the future.

Simplifies unit testing of every separate screen in the application.

Development of the system can be done by quite a large team of developers simultaneously.

The system can be easily adjusted to different customers or different markets needs around the globe.


Temporary inconveniences:

The need to learn a new framework

In rare cases, the debugging of the navigation has to be performed at the level of Lexaden Web Flow engine source code.

Not so suitable for small applications

There is no visual editor for updating flow configuration via the application

There is no compiler allowing you to validate the navigation syntax at compile time.

Currently a sample application is only available for Vaadin component model, although Lexaden Web Flow does not depend on a specific component framework.

Lexaden Web Flow and Lexaden Administration are distributed under terms of Apache License 2.0.

Lexaden
Easy to keep focus on business needs