Skip to content

Modularity Patters with JPMS: Defautl Impl. and Module Facade

Continuing the series about modularization patterns with Java 9 today I would like to introduce the patterns Default Implementation and Module Facade [Kno12]. As its name suggests, the Default Implementation pattern is about providing implementations for abstract modules but also default configuration. Facades are a well-known pattern from [GoF] which can be applied to modules too as we will see.

The Problem

When we try to make modules flexible, we create abstract modules that separate implementations from abstractions and configurations. These now flexible modules are harder to use because users need multiple modules (abstraction and implementation) or external configuration.

Even if there is an implementation provided for a given abstraction, users should not depend on it. We need some mechanism that wires the implementation to the user.

The Solution

The solution to the above problem is to provide default implementations of abstractions and default configuration settings. We deploy default configurations normally within the same module, default implementations can be deployed within the same module or in a separate module.

We should also provide means to obtain default implementations without requiring users to depend on them directly. With the java platform module system (JPMS) we can use services to obtain default implementations as can be seen in the following sample.

Default Implementation Sample

The sales module of our ongoing webshop sample provides an API to apply a discount to the sales. Different implementations are provided by other modules that calculate a discount based on various criteria. In many use cases, however, there won’t be a discount applied. The customer needs to pay the full amount.

As you can see, the sales module provides a default implementation of the discount calculator. It covers the case where no discount needs to be calculated. From the three services that use the sales module, two provide an implementation of the discount calculator and one doesn’t. Another case that I don’t show here and which doesn’t provide an implementation could be the test module.

Without further actions, users need to create this default implementation themselves and therefore have a dependency on it. To prevent this undesired dependency, we have at least two options: Add a factory [GoF] or provide the discount calculator as a service. Because the factory pattern is already well-known, I  choose the service option for this sample.

The following module descriptor of the sales module declares a uses dependency on the discount calculator. At the same time, it provides the default implementation as a service.

module opus.sales {
    exports com.opus.sales;
    uses com.opus.sales.DiscountCalculator
    provides com.opus.sales.DiscountCalculator 
        with com.opus.sales.impl.DefaultDiscountCalculator;
}

The default implementation will be used as long as no other module provides one with a higher priority. When a new requirement arises to provide a different implementation of the discount calculator, it can be implemented in a different module and plugged in the application without changing the sales module.

Default Configuration

In this post, we saw the external configuration pattern which enhances the flexibility of the module. The sample was about configuring the URL of a REST call for different environments. Every developer that wants to use that module needs to provide a configuration for it which makes it harder to use.

To improve the situation, we could add a default configuration inside the module that points at some localhost URL. Then the developers would not need to provide a new configuration each time they want to reuse the module.

Module Façade Pattern

The Module Façade pattern [Kno12] seeks to improve the usability of multiple smaller modules. When we focus on flexibility and reuse, we tend to create many small modules. Those small modules become harder to use because of their fine granularity.

A module façade provides a common API for multiple smaller modules. It may also contain configuration for them and even add functionality itself.

Sample Facade

Suppose we want to manage different product categories as independent modules. For example, we could have a home electronics module and a pc module. They both share common functionality of the product base module but add their own search and filter options.

To ease the use of those product category modules, we create a product module as a façade which includes the home electronics, pc and base modules. Users now only need to add a dependency on the façade without worrying about the different categories. When more categories are added, users automatically get access to them too without needing to modify their dependencies.

Wrap Up / Final Thoughts

When we want to use a module, it should work out of the box. If we need to find an implementation of an abstract module or need to add multiple dependencies to make it work, we are distracted from our actual task. If we design modules according to the two usability patterns default implementation and module façade, live for the users of our modules (which could be ourselves too) is greatly simplified.

[Kno12] K. Knoernschield: Java Application Architecture – Modularity Patterns with Examples Using OSGi (homepage)

[GoF] Design Patterns: Elements of Reusable Object-Oriented Software (wiki)

An den Anfang scrollen