In this section, practical development steps are discussed for anyone interested in contributing to xoom-designer
.
The following diagram gives us an Application Generation
overview showing the interaction of its components:
As illustrated above, Application Generation
can be run by two commands: xoom gen
or xoom gui
. Both are alternative ways for a quick start with VLINGO XOOM
, having exactly the same parameters list. The only difference is that the latter reads parameters from a properties file, while the first consumes parameters from a web-based UI.
The second half of the diagram shows some tools that perform core actions. Maven Archetypes creates the project structure, dynamically organizing the directory hierarchy, Maven configuration, also handling deployment resources as Dockerfile and K8s manifest file. Further, Apache FreeMarker takes care of classes generation by processing preexisting code templates. That said, let's see how to add templates at code level.
The main constituent parts for every auto-generated class are:
- A Freemarker template file
- A io.vlingo.xoom.turbo.codegen.template.TemplateData implementation
- A io.vlingo.xoom.turbo.codegen.template.TemplateProcessingStep implementation
Considering those parts, let's take RestResource
class generation as an example and go through the implementation details, starting from the template file:
package ${packageName};
import io.vlingo.xoom.http.resource.Resource;
import static io.vlingo.xoom.http.resource.ResourceBuilder.resource;
public class ${resourceName} {
public Resource<?> routes() {
return resource("${resourceName}" /*Add Request Handlers here as a second parameter*/);
}
}
As easy as it seems, the Rest Resource template file requires only two parameters values to generate a Rest Resource
class: packageName
and resourceName
. The parameters handling and mapping are addressed by RestResourceTemplateData as follows:
public class RestResourceTemplateData extends TemplateData {
private final static String PACKAGE_PATTERN = "%s.%s";
private final static String PARENT_PACKAGE_NAME = "resource";
private final String packageName;
private final String aggregateName;
private final TemplateParameters parameters;
public RestResourceTemplateData(final String aggregateName,
final String basePackage) {
this.aggregateName = aggregateName;
this.packageName = resolvePackage(basePackage);
this.parameters = loadParameters();
}
private TemplateParameters loadParameters() {
return TemplateParameters
.with(REST_RESOURCE_NAME, REST_RESOURCE.resolveClassname(aggregateName))
.and(PACKAGE_NAME, packageName);
}
private String resolvePackage(final String basePackage) {
return String.format(PACKAGE_PATTERN, basePackage, PARENT_PACKAGE_NAME).toLowerCase();
}
@Override
public TemplateStandard standard() {
return REST_RESOURCE;
}
@Override
public TemplateParameters parameters() {
return parameters;
}
@Override
public String filename() {
return standard().resolveFilename(aggregateName, parameters);
}
}
RestResource
classes should be placed under its own package. Hence, the resolvePackage
method appends the project base package to the resource
package. The full package name and the RestResource
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.
public class RestResourceGenerationStep extends TemplateProcessingStep {
@Override
protected List<TemplateData> buildTemplateData(final TaskExecutionContext context) {
final String projectPath = context.projectPath();
final String basePackage = context.propertyOf(Property.PACKAGE);
final String restResourcesData = context.propertyOf(Property.REST_RESOURCES);
return RestResourceTemplateDataFactory.build(basePackage, projectPath, restResourcesData);
}
@Override
public boolean shouldProcess(final TaskExecutionContext context) {
return context.hasProperty(Property.REST_RESOURCES);
}
}
RestResourceGenerationStep implements buildTemplateData
method that passes parameter values, coming from the Web-based UI or properties file, to RestResourceTemplateData. In this particular scenario, RestResourceTemplateDataFactory is an additional and optional class that helps building RestResourceTemplateData. The shouldProcess method is also optional and useful when a TemplateProcessingStep subclass needs to be conditionally skipped.
Finally, TemplateProcessingStep has to be added in the Configuration steps list:
public static final List CODE_GENERATION_STEPS = Arrays.asList(
new ModelGenerationStep(),
new ProjectionGenerationStep(),
new StorageGenerationStep(),
new RestResourceGenerationStep(),
new BootstrapGenerationStep(),
new ContentCreationStep()
);
Eventually, some peripheral points in the code are also involved. The following list is mainly related to a template file creation:
- Create an enum value in Template passing the template filename (without extension) in the constructor. Example:
public enum Template {
//Other template filenames
REST_RESOURCE("RestResource")
//Enum attributes
}
- 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 eachStorage
(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 example below demonstrates theAggregate
andRest Resource
standards. The latter has only one related template file:
public enum TemplateStandard {
AGGREGATE(parameters -> AGGREGATE_TEMPLATES.get(parameters.from(STORAGE_TYPE))),
REST_RESOURCE(parameters -> CodeTemplateFile.REST_RESOURCE.filename),
//Other standards
}
- In case it doesn't already exist, create an enum value in TemplateParameter for each template parameter.
In sum, those are the common steps regarding code template files
on xoom-designer
. Our team is available to discuss and provide more information on Gitter.