Skip to main content

Module Models

In some cases, models have parts that can be reused over multiple programs. The elements encasing the common parts are referred to as Modules.

  • Modules can encapsulate a concern, which can then be reused in multiple Programs of the same type. E.g. the account base-component is a module that provides authentication and authorization.
  • Modules can define a common model shared among multiple different Programs which are part of a larger system. E.g. the Module describes a message interface. This can then be used in the consumer Program to generate message processors, and the producer Program to generate clients.

A Module has a ElementClass and a ModuleType, which is linked to a ProgramType.

  • ElementClass: ElementClass, with role module.
  • ModuleType: description of how the Module works within the ProgramType.

Modules are also used in the elements metamodel and the JEE Application stack:

  • Component is a module ElementClass, described in elements.
  • There is a JeeComponent, linked to JeeApplication. Similar to the ProgramType, it describes how to load the Modules, where to expand and which Layers to include.
ProgramTypes and ModuleTypes

One ProgramType can have multiple ModuleTypes. In this case each ModuleType is processed in sequence.

One ElementClass can also have multiple ModuleTypes. This can be multiple ModuleTypes across different ProgramTypes (i.e. the Module can be reused in different situations) or can be multiple ModuleTypes for the same ProgramType (in case some Modules need to be loaded differently).

Defining Modules

To model a new class of Modules, define an ElementClass with role module. You will also need an aggregation reference to reference the modules in the program

model/elementClasses/ScriptModule.xml
<elementClass name="ScriptModule">
<isGloballyUnique>false</isGloballyUnique>
<isAbstract>false</isAbstract>
<isSealed>false</isSealed>
<group name="custom" ontology="demo"/>
<role name="module"/>
<attributes>
<attribute name="name">
<isIdentifier>true</isIdentifier>
<dataType name="Name" ontology="demo"/>
</attribute>
</attributes>
</elementClass>

After expansion, you'll need a few extra customizations:

ModuleFactory

Similar to the ProgramFactory, the ModuleFactory is a class that provides implementations to import Modules.

   ModuleFactory example

public class ScriptModuleFactory implements ModuleFactory<ScriptModuleDataRef, ScriptModuleTree> {

/*
Several Classes are expanded for Module ElementClasses.
In most cases, it is enough to use these classes directly.
*/
@Override
public ModuleReader<ScriptModuleDataRef, ScriptModuleTree> getModuleReader(
ModelLoadingContext modelLoadingContext, ModuleTypeComposite moduleType) {
return new ScriptModuleModuleReader(modelLoadingContext, moduleType);
}

@Override
public ModuleLoader<ScriptModuleTree> getModuleLoader(
ModelLoadingContext modelLoadingContext, ModuleTypeComposite moduleType) {
return new ScriptModuleModuleLoader(modelLoadingContext, moduleType);
}

@Override
public ModuleEnricher getModuleEnricher(
ModelLoadingContext modelLoadingContext, ModuleTypeComposite moduleType) {
return new ScriptModuleModuleEnricher(modelLoadingContext, moduleType);
}

@Override
public ModuleWriter<ScriptModuleTree> getModuleWriter(
ModelLoadingContext modelLoadingContext, ModuleTypeComposite moduleType) {
return new ScriptModuleModuleWriter(modelLoadingContext, moduleType);
}
}

ModuleRetriever

Update the getModuleRetriever() method in the ProgramFactory to return the correct module references. You can check the expandableElement reference to match the ElementClass

   ModuleRetriever example

  @Override
public ProgramModuleRetriever<ExecutableTree, ModuleTypeComposite> getModuleRetriever(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return ((executable, moduleType) -> {
if (moduleType.getExpandableElement().getDataRef().toString().equals("demo::ScriptModule")) {
return executable.getScriptModules();
}
return Collections.emptyList();
});
}

ModuleTypes.xml

The metamodel project contains a moduleTypes.xml file with a custom anchor to add ModuleTypes.

  • name and component are combined to make a unique key.
  • expandableElement refers to the ElementClass in the metamodel.
  • moduleFactory is the fully qualified name of the ModuleFactory implementation.
  • modelDirectory is a template for the location of the model files.
  • sourceDirectory describes the location for harvest and ext files.
  • expansionDirectory is used for the target directory for Expanders.
  • layerTypes lists the layerTypes of the Module.

   ModuleType example

<dataResource type="expansionControl::ModuleType">
<!-- anchor:custom-program-types:start -->
<moduleType name="Java Cli Script Module">
<component>demo</component>
<programType component="demo" name="Java Cli"/>
<expandableElement component="demo" name="ScriptModule"/>
<moduleFactory>org.normalizedsystems.demo.ScriptModuleFactory</moduleFactory>
<modelDirectory>$source.rootDirectory$/modules/$scriptModule.name$/model</modelDirectory>
<sourceDirectory>$source.rootDirectory$/modules/$scriptModule.name$</sourceDirectory>
<expansionDirectory>$program.expansionDirectory$/src/main/java/$scriptModule.name;format="toPath"$</expansionDirectory>
<layerTypes>
<layerType name="EXEC_ROOT"/>
<layerType name="EXEC_CLI"/>
<layerType name="EXEC_LOGIC"/>
<layerType name="EXEC_SHARED"/>
</layerTypes>
</moduleType>
<!-- anchor:custom-program-types:end -->
</dataResource>
Module Layers

Like Layers in the Program, each ModuleType also lists each applicable LayerType. For Module Elements and all contained Elements, the LayerTypes of the Module will be used, not the Program.

Hence, if an Expander targets an ElementClass that occurs within a Module, it should also target a LayerType that is defined in at least one of the related ModuleTypes.

Map Technologies

Similar to Programs, Modules contain Technologies, which are used to filter Expanders.

   Example how to add Technologies to a Module

The implementation can be anything, but often the technologies of the Program are used directly.

  // anchor:custom-after-transform:start
module.setTechnologies(module.getProgram().getTechnologies());
// anchor:custom-after-transform:end
Module Technologies

Unlike Programs, Modules do not add COMMON by default. To be able to expand anything, at least one customization will be needed.

Usage in tests

To be able to define test models using a module ElementClass, you will need to add an implementation of the modelSpec(Spec) method. This method will be used when a test only defines a spec for a module, omitting a specification for the Program. The goal of this method in case of a Module is to supply that missing information. The modelSpec also needs a method to retrieve the target Composite from the resulting model after it was built.

   modelSpec() example for a Module

ScriptModuleSpecFactory.java
  // anchor:custom-methods:start
@Override
public ModelSpec<?, ScriptModuleComposite> modelSpec() {
return model(executable("cli", spec))
.setModuleSpecs(List.of(spec))
.setCompositeRetriever(exec -> exec.getScriptModules().get(0));
}
// anchor:custom-methods:end

Next, to be able to use the new Module ElementClasses in the context of a Program test model, the SpecFactory of the Program also needs to be updated. The method should now look for any Specs for a Module and register them as Module Specs.

   Updated modelSpec() example for Program

ExecutableSpecFactory.java
  // anchor:custom-methods:start
@Override
public ModelSpec<?, ExecutableComposite> modelSpec() {
return model(spec)
.setModuleSpecs(spec.getChildSpecs().stream()
.filter(ScriptModuleSpec.class::isInstance)
.map(ScriptModuleSpec.class::cast)
.collect(Collectors.toList()));
}
// anchor:custom-methods:end