Skip to content

15. Multi Module Support

FrankHossfeld edited this page Jul 12, 2023 · 2 revisions

Multi Module Environment

There are several reasons to divide an application into several modules. Reasons might be:

  • separation of work
  • reuse of code
  • faster J2CL compiles.

In the current implementation, you can have a common module, but you do not have to! We will take later a look on how to set up projects.

Creating a module

First we need to create a module. A Nalu module does not implement the IsApplication-interface. Instead, it extends the IsModule-interface. This interface needs a @Module-annotation.

The @Module-annotation takes two attributes:

  1. name: the name of the module.
  2. context: the context of the module

Example:

@Module(name = "myModule01",
        context = MyModule01Context.class)
public interface MyModule01
    extends IsModule<MyModuleContext> {
}

Next we need to tell the application, that there are modules to load. To do so, we add the @Modules-annotation to the IsApplication-interface.

Example:

@Application(startRoute = "/loginShell/login",
             context = MyApplicationContext.class)
@Modules({ MyModule01.class,
           MyModule02.class })
interface MyApplication
    extends IsApplication {

}

The code above tells Nalu, that there are two modules to load:

  • MyModule01
  • MyModule02

Nalu will add all components, routes, etc. of the modules to the application configuration, so that the modules can be used from the main application.

Event

To fire an event that is available in all client modules and the main application, Nalu provides an event class called NaluApplicationEvent. This event takes a String, which should be used to describe the event type and accepts a variable numbers of data - which will be stored inside a map. The map is implemented as a Map<String, Object. Using the key, you can access the map. The map will always return an Object, so you have cast the return value before using it.

F.e.: If you want to update the selected navigation item in the main module (assuming that the main model will provide navigation) from a client submodule, fire a NaluApplicationEvent, set the event string to 'selectNavigationItem' and add the identifier what item inside the navigation is to select as data inside the event store.

The NaluApplicationEvent-class uses a builder pattern. So it is quite easy to create a NaluApplicationEvent.

Here an example of a NaluApplicationEvent, that is named 'selectNavigationItem' and has one data entry: the value 'home' with key 'navigationItem'.

this.eventBus.fireEvent(NaluApplicationEvent.create()
                                            .event("selectNavigationItem")
                                            .data("navigationItem", "home"));

To catch the NaluApplicationEvent, just add a handler to the event bus which will listen to NaluApplicationEvent.TYPE. Cause every application event will be of type NaluApplicationEvent.TYPE, you need to check the value of the attribute. Once catching the event, it is necessary to check if the event is the one of the types you are looking for by comparing the event name with the name of the event you want to catch!

this.eventBus.addHandler(NaluApplicationEvent.TYPE,
                         e -> {
                           if ("selectNavigationItem".equals(e.getEvent())) {
                             String selectedItem = (String) e.getData("navigationItem");
                             // do something ... 
                           }
                         });

Note: Keep in mind, that you have to cast the stored object to the right type before using it.

Context

To avoid the need of a common module which contains an application wide context, all of your contexts need to extend AbstractModuleContext. The AbstractModuleContext-class owns a data map object wrapped inside a ContextDataStore. This data map object will be injected in every context. All data contained in this store is application wide available.

A simple context might look like that:

public class MyApplicationContext
  extends AbstractModuleContext {
  
  private final static String ATTRIBUTE_KEY = "attribute";
  
  public MyApplicationContext() {
  }
  
  public String getAttribute() {
    return (String) this.getContext().get(MyApplicationContext.ATTRIBUTE_KEY);
  }
  
  public void setAttribute(String attribute) {
    this.getContext().put(MyApplicationContext.ATTRIBUTE_KEY, attribute);
  }
}

Of course, you can save the code for the getter- and setter-methods and access directly the map:

String attribute = (String) myApplicationContext.getContext().get(MyApplicationContext.ATTRIBUTE_KEY);

In case you need value only available in the current module or the main module, you can add them as a normal attribute with Getter- and Setter-methods:

public class MyApplicationContext
  extends AbstractModuleContext {
  
  private final static String ATTRIBUTE_KEY = "attribute";
  
  private String localAttribute;
  
  public MyApplicationContext() {
  }
  
  public String getAttribute() {
    return (String) this.getContext().get(MyApplicationContext.ATTRIBUTE_KEY);
  }
  
  public void setAttribute(String attribute) {
    this.getContext().put(MyApplicationContext.ATTRIBUTE_KEY, attribute);
  }
  
  public String getLocalAttribute() {
    return this.localAttribute;
  }
  
  public void setLocalAttribute(String localAttribute) {
    this.localAttribute = localAttribute;
  }
}

Note

This implementation might look a little bit boiler-plated, but it helps you to avoid a common project where all modules and the main module depend on!

Loader

The module loaders will be called after the loader of the main module! Inside the application module, you can add a post loader, so that - after all module loaders are executed - another application loader will be executed.

Set Up a Multi Module Project

There are several ways to set up a Nalu multi module project.

Thomas Broyer GWT Archetype

In case your project is based on the Thomas Broyer GWT Maven Archetype it is quite easy to work with Nalu modules. Use the shared-module as copy base for your new module and add it as source project to your client-module pom (similar to the shared-module).

Keep in mind, that your package inside the modules should be the same as the one inside the client-module.

For example:

  • client-module-package: com.github.nalukit.app.client
  • shared-module-package: com.github.nalukit.app.shared
  • modules-module-package: com.github.nalukit.app.module

Besides that, you need to add: to your module descriptor.

Using this set up, you can edit sources inside your module. Reloading the browser will recompile the sources of the module too!