Fabian Trampusch Blog

» Knowledge through passion. «

Extensible View-Models for FreeMarker in Java

Hello there! The team I work in, jStage, develops a module-based eCommerce platform. This article will summarize some thoughts about composable and typesafe extensions to our view model classes without using inheritance. Our shop system is used by different business clients, each having their own requirements. We can customize our shop system to supply business clients the required functionality through our module-based architecture. This article gives a quick glance on how we achieved a stable API on our View-Models in the frontend, while being able to extend them for each business client specific requirements.

In our architecture, the frontend is decoupled from the backend by the so-called View-Models. The result of each request is a rendered view. To render a view, we need the view template (HTML + Freemarker) and the ViewModels (Java objects containg the data).

A simple view template in Freemarker may look like this:

<#if articleViewModel.isOnWatchlist>
  ${articleViewModel.watchlistUrl}
</#if>

The corresponding ViewModel class may look like this:

public class ArticleViewModel {

    private boolean isOnWatchlist;
    private String watchlistUrl;
    
    public SomeViewModel(boolean isOnWatchlist, String addToWatchlistUrl) {
        this.isOnWatchlist = isOnWatchlist;
        this.addToWatchlistUrl = addToWatchlistUrl;
    }

    // getter and setter omitted

}

Our shop system is following a module-based architecture. The views may be programmed by our business clients or by us. To support the frontend developers, we need to have a nice way to document the information that view models provide. We use JavaDoc and Doclets to generate the documentation out of the source code. It is crucial that this documentation only contains the methods and variables of our View-Models that are present in the corresponding business client specific build of our shop system.

The module based approach is crucial to support multiple business clients. Each business client has distinct requirements. We try to map functionality required by our business clients to existing modules. Sometimes, we therefore have to develop new modules if the requirements do not fit in existing modules. A module is therefore used by at least one business client, but may also be used by more than one. Crucial modules, like the authentication module, are used by all business clients.

Lets discuss how that module based approach works. Our shop provides a module with the ability to display articles. Articles do have some basic information, like a translatable name. To render an article, we need to provide a view (customer specific) and the information to display in form of a View-Model (not customer specific). In some of the shops of our business clients, an article may be added to a watchlist. Customers get notifications if an article on the customers watchlist changes, e.g. if the price changes. The crucial part to understand here is, that the watchlist feature is an optional module. The composition of modules is customer specific, and some modules provide features that extend the ability of other modules.

Lets have a look on how to model that. First, we need a corresponding ArticleViewModel class. This could be located in a module called article, which handles other article-related operations, e.g. database lookups. Because not all shops do need a watchlist, the watchlist management is provided by a separate watchlist module. This module knows what an article is (and has therefore a dependency on the article module), but the article module does not know the watchlist module. This is common in modular architecture, but notable is, that other modules provide operations, which are centred around models of other modules, e.g. the watchlist allows to watch articles. The really interesting question is now, how can other modules add information to a given view model? One could try to solve this problem using inheritance (WatchlistableArticleViewModel extends ArticleViewModel), but you fail with this approach as soon as you have more than one module trying to extend a view model. You could still try to solve this issue by writing an ArticleViewModel class per business client, but this would clearly lead to much duplicated code.

The proposed solution

I will present the classes necessary first and describe the usage afterwards.

// this class contains the information which shall be added to other view models
public class WatchlistArticleViewModelExtension {

    private boolean isOnWatchlist;
    private String watchlistUrl;
    
    public WatchlistArticleViewModelExtension(boolean isOnWatchlist, String addToWatchlistUrl) {
        this.isOnWatchlist = isOnWatchlist;
        this.addToWatchlistUrl = addToWatchlistUrl;
    }

    // getter and setter omitted

}
// this interface must be implemented to extend ViewModels of type VM (see the first generic paramter).
// The information added is an instance of type T (see the second generic paramter).
// Do not worry if you do not immediately understand this, the examples should clarify this soon.
public interface ViewModelExtensionDescriptor<VM, T> {

    /**
     * The key used in the extension map (and therefore the name of the extension for accessing in templates).
     * @return the extension map key.
     */
    String getKey();

}
// This class contains the meta data to link the extension (WatchlistArticleViewModelExtension) with
// the class which shall be extended (ArticleViewModel).
public class WatchlistExtensionDescriptor implements
        ViewModelExtensionDescriptor<ArticleViewModel, WatchlistArticleViewModelExtension> {

    @Override
    public String getKey() {
        return "watchlist";
    }

}
// This is the base class of all extensible View-Models (like the earlier mentioned ArticleViewModel)
// inheriting classes need to set the VM-Parameter to their class, so the ArticleViewModel
// would extend AbstractExtensibleViewModel<ArticleViewModel>.
public abstract class AbstractExtensibleViewModel<VM> {

    private Map<String, Object> extensions = new HashMap<>();

    // this method allows the extension of this view model instance
    public <T> void addExtension(Class<? extends ViewModelExtensionDescriptor<? extends VM, T>> viewModelExtension,
                                 T value) {
        try {
            extensions.put(viewModelExtension.newInstance().getKey(), value);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    // This method is used by freemarker to have easier access to the properties provided by extensions
    // have a look at http://freemarker.org/docs/pgui_misc_beanwrapper.html#beanswrapper_hash
    public Object get(String key) {
        return extensions.get(key);
    }
        
    public <T> T getExtension(Class<? extends ViewModelExtensionDescriptor<? extends VM, T>> viewModelExtension) {
        try {
            return (T) extensions.get(viewModelExtension.newInstance().getKey());
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    
}

Usage

To extend a ViewModel, it has to inherit from AbstractExtensibleViewModel. After that, you can declare an extension in Java like this:

// set the extension:
WatchlistArticleViewModelExtension extension = new WatchlistArticleViewModelExtension(false, "some url here");
ArticleViewModel articleViewModel = new ArticleViewModel();
articleViewModel.addExtension(WatchlistExtensionDescriptor.class, extension);

// get the extension:
WatchlistArticleViewModelExtension extension = articleViewModel.getExtension(WatchlistExtensionDescriptor.class);

The corresponding Freemarker-Template could look like this:

<#if articleViewModel.watchlist.isOnWatchlist>
  ${articleViewModel.watchlist.watchlistUrl}
</#if>

Summary

To recap: This solution may not be as lightweight as a simple extension-Map-approach with naive Key-Value-Extensions, but it provides the following benefits:

  • programmers do not write ad hoc extensions (create lots of keys and throw in values needed somewhere, making it hard to see which extensions are available)
  • it is easier for the programmer to see all existing extensions (just show inheriting classes of ViewModelExtensionDescriptor)
  • this approach is documentable and can be used for automatic documentation generation
  • you have type-safety in the Java world
  • you have easy access in the template world, especially with Freemarkers “generic get methods”
  • it maximizes composability between modules
  • it is more flexible than simple inheritance
  • extensions and view models are as reusable as possible

Contact me in the comments if you have questions, critique or other ideas regarding this approach.