Development Guide
In this section, practical development steps are discussed for anyone interested in contributing to the VLINGO XOOM Designer project.

Features

The XOOM Designer codebase mainly contains the implementation of the following features:
    XOOM Designer: the visual model designer itself that, besides the project generation, provides a rapid configuration for VLINGO XOOM components.
    XOOM CLI: provides shortcuts for initializing XOOM Designer and interacting with Docker, Gloo Gateway API, and Kubernetes;
Although sharing the same codebase, these two features are not strongly dependent, so it's meant to be kept semantically and structurally separate. In that sense, the Designer and CLI implementations are respectively placed under the package io.vlingo.xoom.designer and io.vlingo.xoom.cli.
Next, the practical sections show how to maintain and expand both features.

Introduction to CLI

Once the XOOM Designer is correctly installed, the CLI is accessed from the terminal by calling the executable bash script (ex. ./xoom docker package). Internally, this script runs the Designer jar and passes the command, i.e. docker package, to the CommandLineInterfaceInitializer class:
1
public class CommandLineInterfaceInitializer {
2
3
public static void main(final String[] args) {
4
...
5
6
final Task task = Task.triggeredBy(resolveCommand(args));
7
8
runTask(task, Arrays.asList(args));
9
}
10
11
...
12
}
Copied!
The code snippet above shows that an implementation of io.vlingo.xoom.cli.task.Task is triggered by the user command, implying that there is one Task subclass for each supported task. Next, the task implementation responsible for initializing the Designer service is demonstrated:
1
public class DesignerTask extends Task {
2
3
private final DesignerInitializer initializer;
4
5
...
6
7
@Override
8
public void run(final List<String> args) {
9
this.initializer.start(OptionValue.mapValues(options, args));
10
}
11
12
}
Copied!
The OptionValue class helps tasks to support execution options, which are passed along with the bash command. For instance, the designer server port can be customized as follows:
1
$ ./xoom designer --port 8081
Copied!
The concluding step of a Task implementation is to edit the ComponentRegistration class mapping the task as an element of the cliTasks list. That makes XOOM CLI able to run the task when the corresponding command is executed:
1
2
public class ComponentsRegistration {
3
4
public static void registerWith(final Logger logger,
5
final CommandExecutionProcess commandExecutionProcess,
6
final XoomTurboProperties properties) {
7
ComponentRegistry.register("cliTasks", Arrays.asList(new DesignerTask(commandExecutionProcess), new DockerPackageTask(commandExecutionProcess) ...);
8
}
Copied!

Introduction to Designer

The following diagram gives us an overview of how the Designer components interact for generating a project:
XOOM Designer components
The Designer-embedded user interface illustrated above is built with Svelte. It consumes a Rest API submitting the model details to the server-side. Once successfully processed, XOOM Designer uses Apache FreeMarker for generating classes, configuration, and deployment files. That said, let's see how to add templates at the code level.
For any development on XOOM Designer you must set an environment variable named VLINGO_XOOM_STARTER_HOME. Although you have likely already set this property in order to use the XOOM Designer, that was as an enduser, which has a different value. As a developer working on the Designer, you must set this to the absolute path of a directory relative to where the vlingo-xoom-starter repository has been cloned.
Using a *nix shell, such as bash, set like this:
1
$ VLINGO_XOOM_DESIGNER_HOME=[git-clone-path]/dist/designer
2
$ export VLINGO_XOOM_DESIGNER_HOME
Copied!
On Windows you can use the System Properties > Advanced > Environment Variables... to set the property permanently. For a one-time setting before running the design tool you can use the command line:
1
C:\> set VLINGO_XOOM_DESIGNER_HOME=[git-clone-path]\dist\designer
Copied!
After making changes, from the root project directory, build and run the Designer. On *nix run these commands:
1
$ mvn clean package -P frontend
2
...
3
$ java -jar target/xoom-designer-1.6.1-SNAPSHOT.jar gui
Copied!
This works on Windows:
1
C:\[git-clone-path]> mvn clean package -P frontend
2
...
3
C:\[git-clone-path]> java -jar target\xoom-designer-1.6.1-SNAPSHOT.jar gui
Copied!
For more details see README.md in the xoom-designer repository.

Create / Update Code Templates

The main constituent parts for every auto-generated class are:
Considering those parts, let's take AggregateProtocol class generation as an example and go through the implementation details, starting from the template file:
1
package ${packageName};
2
3
<#if imports?has_content>
4
<#list imports as import>
5
import ${import.qualifiedClassName};
6
</#list>
7
</#if>
8
9
public interface ${aggregateProtocolName} {
10
<#if !useCQRS>
11
12
/*
13
* Returns my current state.
14
*
15
* @return {@code Completes<${stateName}>}
16
*/
17
Completes<${stateName}> currentState();
18
</#if>
19
20
}
Copied!
The Aggregate Protocol template file requires some parameter values to generate an Aggregate Protocol class. The parameters handling and mapping are addressed by AggregateProtocolTemplateData as follows:
1
public class AggregateProtocolTemplateData extends TemplateData {
2
3
private final String protocolName;
4
private final TemplateParameters parameters;
5
6
public AggregateProtocolTemplateData(final String packageName,
7
final CodeGenerationParameter aggregate,
8
final List<Content> contents,
9
final Boolean useCQRS) {
10
this.protocolName = aggregate.value;
11
this.parameters = TemplateParameters.with(TemplateParameter.PACKAGE_NAME, packageName)
12
.addImports(resolveImports(aggregate, contents))
13
.and(TemplateParameter.AGGREGATE_PROTOCOL_NAME, aggregate.value)
14
.and(TemplateParameter.STATE_NAME, JavaTemplateStandard.AGGREGATE_STATE.resolveClassname(aggregate.value))
15
.and(TemplateParameter.USE_CQRS, useCQRS);
16
}
17
18
private Set<String> resolveImports(final CodeGenerationParameter aggregate, final List<Content> contents) {
19
return ValueObjectDetail.resolveImports(contents, aggregate.retrieveAllRelated(Label.STATE_FIELD));
20
}
21
22
@Override
23
public String filename() {
24
return standard().resolveFilename(protocolName, parameters);
25
}
26
27
@Override
28
public TemplateParameters parameters() {
29
return parameters;
30
}
31
32
@Override
33
public TemplateStandard standard() {
34
return JavaTemplateStandard.AGGREGATE_PROTOCOL;
35
}
36
37
}
Copied!
The full package name and the AggregateProtocol class name are mapped to the template parameters in loadParameters. Additionally, TemplateData requires the filename method implementation, which commonly uses the filename resolution logic in the corresponding TemplateStandard.
1
public class ModelGenerationStep extends TemplateProcessingStep {
2
3
@Override
4
protected List<TemplateData> buildTemplatesData(final CodeGenerationContext context) {
5
return ModelTemplateDataFactory.from(context);
6
}
7
8
@Override
9
public boolean shouldProcess(final CodeGenerationContext context) {
10
return context.hasParameter(Label.AGGREGATE);
11
}
12
13
}
Copied!
ModelGenerationStep implements the buildTemplateData method that passes parameter values, coming from the Web-based UI, to RestResourceTemplateData. In this particular scenario, ModelTemplateDataFactory is an additional and optional class that helps building AggregateProtocolTemplateData. The shouldProcess method is also optional and useful when a TemplateProcessingStep subclass needs to be conditionally skipped.
Finally, TemplateProcessingSteps has to be added to the Configuration steps list:
1
2
private static List<CodeGenerationStep> codeGenerationSteps() {
3
return Arrays.asList(
4
...
5
//Java
6
new ReadmeFileGenerationStep(),
7
new ApplicationSettingsGenerationStep(),
8
new ValueObjectGenerationStep(),
9
new ModelGenerationStep(),
10
new DataObjectGenerationStep()
11
12
....
13
);
14
Copied!
Eventually, some peripheral points in the code are also involved. The following list is mainly related when a new template file is added:
1. Create an enum value in Template passing the template filename (without extension) in the constructor. Example:
1
2
public enum Template {
3
4
//Other template filenames
5
6
AGGREGATE_PROTOCOL("AggregateProtocol")
7
8
//Enum attributes
9
}
10
Copied!
2. Map the new standard file to an existing TemplateStandard or create one. Sometimes there are multiple files for the same standard. For instance, there is one Aggregate template file for each Storage (Journal, State Store, Object Store). That means TemplateStandard is responsible for grouping template files by standard and helps the TemplateProcessor to find the proper file based on TemplateParameters such as StorageType. The examples below demonstrate the Aggregate Protocol and Value Object standards.
1
public enum JavaTemplateStandard {
2
3
AGGREGATE_PROTOCOL(parameters -> Template.AGGREGATE_PROTOCOL.filename),
4
VALUE_OBJECT(parameters -> Template.VALUE_OBJECT.filename),
5
6
//Other standards
7
}
8
Copied!
3. In case it doesn't already exist, create an enum value in TemplateParameter for each template parameter.
To sum up, those are the common steps regarding code template files on xoom-designer. Our team is available to discuss and provide more information on Gitter and our public Slack workspace.
Last modified 11d ago