Modules

Introduction

Enabling and ensuring modularity is a key principle for the Apache Causeway framework. Modularity is the only way to ensure that a complex application domain does not over time degenerate into the infamous "big ball of mud", software that is difficult, dangerous and expensive to change.

Modules chunk up the overall application into smaller pieces, usually a package with subpackages. The smaller pieces can be either:

  • horizontal tiers (presentation / application / domain / persistence) or

  • vertical functional slices (eg customer vs orders vs products vs invoice etc).

Because Apache Causeway takes care of the presentation and persistence tiers, modules for us correspond to vertical functional slices. The framework is intended to support complex domains, which we tackle by breaking that domain down into smaller subdomains, in other words into modules.

It could be argued that there is still an application tier (view models) and a domain tier (entities) to be considered, as well as mixins. We describe the structure of an archetype module below, showing one way of organising the horizontal tiers within the vertical slices.

Having broken the application down into smaller modules, these modules will then depend upon each other. The two main rule of thumbs for dependencies are:

  1. there should be no cyclic dependencies (the module dependencies should form an acyclic graph), and

  2. unstable modules should depend upon stable modules, rather than the other way around.

By "unstable" we don’t mean buggy, rather this relates to its likelihood to change its structure or behaviour over time: in other words its stability as a core set of concepts upon which other stuff can depend. Reference data (calendars, tax rates, lookups etc) are generally stable, as are "golden" concept such as counterparties / legal entities or financial accounts. Transactional concepts such as invoices or agreements is perhaps more likely to change. But this stuff is domain specific.

Programming Model

In Apache Causeway we use Spring @Configurations to define a module, consisting of a set of domain services and domain objects (entities, view models and mixins). Spring’s @Import is used to express a dependency between each "configuration" module.

For example:

package com.mycompany.modules.customer;
...

@Configuration                                  (1)
@Import({
    CausewayModuleExtExcelApplib.class          (2)
})
@ComponentScan                                  (3)
@EntityScan                                     (4)
public class CustomerModule {}
1 this is a module
2 dependency on another module, in this case the Excel module provided by Apache Causeway itself.
3 scan for domain services and objects etc under this package (eg a Customer entity or a CustomerRepository domain service).
4 scan for domain entities. This is required in applications that use JPA/EclipseLink as their ORM.
See Spring documentation on annotation-based container configuration, classpath scanning and java-based configuration for much more on this topic.

Since @Configuration and @ComponentScan often appear together, Apache Causeway provides a convenience @Module annotation, which is meta-annotated with both. The above example could therefore be rewritten as:

package com.mycompany.modules.customer;
...
import org.apache.causeway.applib.annotation.Module;

@Module
@Import({
    CausewayModuleExtExcelApplib.class
})
@EntityScan
public class CustomerModule {}

Maven modules

By convention, we have just one Spring module to each Maven module. This means that the <dependency>`s between Maven modules are mirrored in the Spring module’s `@Import statements). We can therefore rely on Maven to ensure there are no cyclic dependencies: the application simply won’t compile if we introduce a cycle.

If the above convention is too officious, then you could choose to have multiple Spring modules per Maven module, but you will need to watch out for cycles.

In such cases (proprietary) tools such as Structure 101 can be used to help detect and visualize such cycles. Or, (open source) libraries such as ArchUnit or jQAssistant can help enforce architectural layering to prevent the issue arising in the first place. (These tools can enforce other conventions, too, so are well worth exploring).

We recommend a single top-level package corresponding to the module, aligned with the <groupId> and <artifactId> of the Maven module in which it resides. This top-level package is also where the Spring @Configuration module class resides.

Apache Causeway’s own modules follow this convention. For example, the Excel extension module has several submodules, one of which is its applib:

  • its Maven groupId:artifactId is:

    org.apache.causeway.extensions:causeway-extensions-excel-applib

  • in the applib, its top-level package is:

    org.apache.causeway.extensions.excel.applib

  • it defines a Spring configuration module called:

    CausewayModuleExtExcelApplib

    which looks like:

    @Configuration
    @ComponentScan                                  (1)
    @EntityScan                                     (2)
    public class CausewayModuleExtExcelApplib {
    }
    1 the @ComponentScan indicates that the classpath should be scanned for domain services, entities and fixture scripts.
    2 recommended if using JPA/EclipseLink, skip if using JDO.

When there is a dependency, this is then expressed in two ways: first, as a "physical" <dependency> in Maven; and second, as a "logical" dependency using @Import in the @Configuration module.

Looking again at the Excel library once more, this has an applib module and also a testing (artifactId of causeway-extensions-excel-testing), where:

Testing submodule depends upon Applib submodule
Figure 1. Testing submodule depends upon Applib submodule

Therefore:

  • in the testing module’s pom.xml, we see:

    <dependency>
        <groupId>org.apache.causeway.extensions</groupId>
        <artifactId>causeway-extensions-excel-applib</artifactId>
    </dependency>
  • and in the testing module’s CausewayModuleExtExcelTesting we see:

    @Configuration
    @Import({                                           (1)
        CausewayModuleExtExcelApplib.class
    })
    @ComponentScan
    public class CausewayModuleExtExcelTesting {
    }
    1 The @Import annotation declares the dependency.

Unlike Maven, there is no distinction in Spring between production (src/main/java) and test (src/test/java) classpath dependencies. But if the physical classpath dependency is missing, then an incorrectly defined @Import will simply not compile.

Untangling the Terminology

Modularity is a rather overloaded term. We have Maven modules, we have Spring @Configuration modules, and we also have JDK (Jigsaw) modules, introduced in Java 9.

In the context of Apache Causeway, a module is first and foremost a Spring @Configuration module. If you follow our recommendation of a single Spring module in each Maven module, then an Apache Causeway module is also a Maven module.

All of the Apache Causeway modules are also Jigsaw modules, but you may not find there’s much benefit to be gained from doing the work to make your own application modules into Jigsaw module also. The benefit if you do is much stronger access control to ensure that modules only programmatically call each other in well-defined ways. Using the archetypal module structure described below will help.

AppManifest

There needs to be one top-level module that references all of the modules that make up the application, either directly or indirectly (through transitive dependencies). In Apache Causeway we call this module the "app manifest" or AppManifest, though this is just a name: it is, ultimately, just another Spring @Configuration module.

For example, in the simpleapp starter app the AppManifest looks like:

AppManifest.java
package domainapp.webapp;

//...

@Configuration
@Import({
        CausewayModuleApplibMixins.class,
        CausewayModuleApplibChangeAndExecutionLoggers.class,

        CausewayModuleCoreRuntimeServices.class,
        CausewayModulePersistenceJpaEclipselink.class,
        CausewayModuleViewerRestfulObjectsJaxrsResteasy.class,
        CausewayModuleViewerWicketApplibMixins.class,
        CausewayModuleViewerWicketViewer.class,

        CausewayModuleTestingFixturesApplib.class,
        CausewayModuleTestingH2ConsoleUi.class,

        CausewayModuleExtFlywayImpl.class,

        CausewayModuleExtSecmanPersistenceJpa.class,
        CausewayModuleExtSecmanEncryptionSpring.class,
        CausewayModuleExtSessionLogPersistenceJpa.class,
        CausewayModuleExtAuditTrailPersistenceJpa.class,
        CausewayModuleExtCommandLogPersistenceJpa.class,
        CausewayModuleExtExecutionLogPersistenceJpa.class,
        CausewayModuleExtExecutionOutboxPersistenceJpa.class,

        CausewayModuleExtExcelDownloadWicketUi.class,
        CausewayModuleExtFullCalendarWicketUi.class,
        CausewayModuleExtPdfjsWicketUi.class,

        CausewayModuleValAsciidocMetaModel.class,
        CausewayModuleValAsciidocUiWkt.class,

        ApplicationModule.class,
        CustomModule.class,
        QuartzModule.class,

        // discoverable fixtures
        DomainAppDemo.class
})
@PropertySources({
        @PropertySource(CausewayPresets.DebugDiscovery),    (1)
})
public class AppManifest {
}
1 there are a whole set of preset configuration properties that you can enable

Decoupling

Having broken up a domain into multiple modules, there is still a need for higher level modules to use lower level modules, and the application must still appear as a coherent whole to the end-user.

The key features that Apache Causeway provides to support this are:

  • dependency injection of services

    Both framework-defined domain services and application-defined services (eg repositories and factories) are injected everywhere, using the @javax.inject.Inject annotation (Spring’s @Autowired can also be used).

    By "everywhere", we mean not just into domain services, but also can be injected into domain entities, view models and mixins. This enables us to implement behaviourally complete domain objects (if we so wish).

  • mixins that allow functionality defined in one module to appear (in the UI) to be provided by some other module.

    For example, a Document module might allow Document objects to be attached to any arbitrary domain object (such as Order or Customer) in other modules. A mixin would allow the UI for a Customer to also display these attached Documents, even though the Customer module would have no knowledge of/dependency on the Workflow module. (More on this example below).

    Dependencies are also injected into mixins. A common technique is to factor out from domain objects into mixins and then generalise.

  • events allow modules to influence other modules.

    A subscriber in one module can subscribe to events emitted by domain objects in another module. These events can affect both the UI (eg hiding or disabling object members, or allowing or vetoing interactions).

Using events we can implement referential integrity constraints within the application. For example, suppose the customers module has a Customer object and a EmailAddress object, with a customer having a collection of email addresses. A communications module might then use those email addresses to create EmailCommunications.

`customers` module and `communications` module
Figure 2. customers module and communications module

If the customers module wants to delete an EmailAddress then the communications module will probably want to veto this because they are "in use" by those EmailCommunications. Or, it might conceivably perform a cascade delete of all associated communications. Either way, the communications module receives an internal event representing the intention to delete the EmailAddress. It can then act accordingly, either vetoing the interaction or performing the cascade delete. The customers module for its part does not know anything about this other module.

Inverting Dependencies

If we get the dependencies wrong (that is, our initial guess on stability is proven incorrect over time) then there are a couple of techniques we can use:

  • use the dependency inversion principle to introduce an abstraction representing the dependency.

  • move functionality, eg by factoring it out into mixins into the other module or into a third module which depends on the other modules that had a bidirectional relationship

Mixins in particular allow dependencies to be inverted, so that the dependencies between modules can be kept acyclic and under control.

For example, suppose that we send out Invoices to Customers. We want the invoices to know about customers, but not vice versa. We could surface the set of invoices for a customer using a Customer_invoices mixin:

`invoices` module contributes to `customers`
Figure 3. invoices module contributes to customers

In the UI, when rendering a Customer, we would also be presented with the associated set of Invoices.

We can also use mixins for dependencies that are in the other direction. For example, suppose we have a mechanism to attach Documents to arbitrary domain objects. The documents module does not depend on any other modules, but provides a DocumentHolder marker interface. We can therefore attach documents to a Customer by having Customer implement this marker interface:

`customers` depends upon contributions of `documents`
Figure 4. customers depends upon contributions of documents

An Archetypal Module

As was discussed in the introductory section above, in Apache Causeway a module corresponds to a vertical slice of functionality. But how should we organise the horizontal tiers (application vs domain) within the module? And how does one module interact with another module?

The diagram below shows a set of packages for a module, for the different concerns:

Diagram
Table 1. Module packages
Pkg Pkg name Description

dom

Domain object model

Holds entities, repositories, supporting services and "local" mixins that act upon those entities

app

Application layer

Holds view models and "manager" objects that describe a business process

menu

Menu services

@DomainService(nature=VIEW); used to fnd entities, create view models ("manager" objects). The entrypoint for end-users

restapi

REST API

@DomainService(nature=REST); entrypoint for clients accessing functionality through the Restful Objects viewer.

api

Application Program Interface

@Service`s and interfaces that define a formal interface for other modules to call this one programmatically. Will call into the functionality defined by the domain objects, might only expose the interfaces defined within the `applib.

applib

Application library

Public interfaces implemented by domain objects in dom, to avoid exposing internal structure. Use by the api, also possibly used by other modules mixin contributions.

contrib

Contributions

Mixins in this module that contribute to other modules. These mixins will use the domain objects in dom (and occasionally view models in app).

spi

Service Provider Interface

Defines hooks that allow other modules to influence behaviour of this module. Called by objects in dom (and perhaps app). The SPI is typically either be an interface of a custom event.

spiimpl

SPI Implementations

Implementations in this module of SPIs defined in other modules. These SPI implementations will likely use the domain object functionality defined in dom.

The following diagram extends the previous, this time showing the interactions between modules:

Diagram

In the above we see that:

  • shipping makes programmatic calls into the api of ordermgmt

  • ordermgmt defines an spi that is implemented by warehouse

    Presumably spiimpl of ordermgmt would implement the spi of some other module, not shown.

  • ordermgmt's mixins in contrib contribute behaviour to all objects that implement the interfaces defined in customer's applib.

  • ordermgmt`s domain objects in `dom implement the interfaces in applib, meaning that other modules can contribute to them.

    They might also implement from the applib of other modules (eg a Customer declaring itself to be a DocumentHolder).