We have a Spring Boot parser application, that supports a bunch of formats. We need to divide this Parser application into several types. Each such type is simply defined by the set of formats supported.
A Provider sends a Message in its own format, which is described in an XML Schema. Example:
Schema:
<Message format="XML">
<metric name="aaa"/>
<metric name="bbb"/>
</Message>
Message:
<Message>
<metric name="aaa">1</metric>
<metric name="bbb">2</metric>
</Message>
For each message format, we have a Deserialized
class and a MessageParser
class. All of the message formats are defined inside enum MessageFormat
.
public enum MessageFormat {
CMS(new CMSMessageParser());
public final MessageParser messageParser;
MessageFormat(final MessageParser messageParser) {
this.messageParser = messageParser;
}
}
Here you can see, that the MessageParser
is tied up with its MessageFormat
.
For example, type
CMS
only should contain CMS
field inside the enum
, type ANY
should support any format, etc.
First, we looked at Spring Profiles:
- They allow to run the
jar
with an option, that will decide whether some of the classes/methods must be loaded into Spring Container or not. The classes/methods must be managed by the Spring Container, but it is quite problematic in our case since all the message parsers are tied toenum
constructor, which is impossible to access by Spring without making up a weird workaround. So, we dropped the idea.
Second, we looked at Maven Profiles:
- They allow to build the
jar
with an option, that will decide which classes to include while compiling (or which compiled classes to delete after compilation). That allows getting thejar
which only contains the classes, that are actually going to be used. No options needed to start up thejar
. We chose this option.
I created a package "maven_replace_by_parser_type" containing different versions of that MessageFormat.java
file. Then, I excluded this package with the maven-compiler-plugin
by default and marked as excluded in the IDE. Then, I created Maven profiles that all do the same thing:
- Overwrite current
MessageFormat.java
with maven_replace_by_parser_type/TYPE_FOLDER/MessageFormat.java
- Compile all classes
- Remove unsupported
.class
files fromtarget
directory. I found out, that Maven refuses to exclude the classes from the compilation, that "have calls to" or "are called by" other non-excluded classes. So, I chose to compile them all, and then clean up the non-needed ones.
This is the pom.xml
with the code, that does exactly what I explained above, but I'm sure there must be a better way!
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.company</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<excludes> <!-- This gets excluded in any case -->
<!-- This contains different "MessageFormat.java" versions for each type -->
<exclude>com/company/product/format/maven_replace_by_collector_type/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<!-- Use maven goal with -P <profile_name> to compile a specific collector -->
<profiles>
<profile>
<id>cms</id>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions> <!-- Replace the MessageFormat.java in the working directory from "format/maven_replace_by_collector_type" -->
<execution>
<id>Replace the MessageFormat.java</id>
<phase>validate</phase> <!-- Before compilation -->
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<copy file="src/main/java/com/company/product/format/maven_replace_by_collector_type/cms/MessageFormat.java"
tofile="src/main/java/com/company/product/format/MessageFormat.java"
overwrite="true"/>
</target>
</configuration>
</execution>
<execution>
<id>Remove everything related to XML, Fixed Length String, JSON and Another Data Provider</id>
<phase>compile</phase> <!-- After compilation -->
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<delete includeemptydirs="true">
<fileset
dir="${project.build.outputDirectory}/com/company/product/"
includes="**/xml*/**, **/Xml*
**/Json*"/>
</delete>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>another data provider</id>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions> <!-- Replace the MessageFormat.java in the working directory from "format/maven_replace_by_collector_type" -->
<execution>
<id>Replace the MessageFormat.java</id>
<phase>validate</phase> <!-- Before compilation -->
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<copy file="src/main/java/com/company/product/format/maven_replace_by_collector_type/another_data_provider/MessageFormat.java"
tofile="src/main/java/com/company/product/format/MessageFormat.java"
overwrite="true"/>
</target>
</configuration>
</execution>
<execution>
<id>Remove everything related to XML, Fixed Length String, JSON and CMS</id>
<phase>compile</phase> <!-- After compilation -->
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<delete includeemptydirs="true">
<fileset
dir="${project.build.outputDirectory}/com/company/product/"
includes="**/xml*/**, **/Xml*
**/FixedLengthString*
**/Json*
**/cms*/**, **/Cms*"/>
</delete>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
It works fine, but it is quite difficult to maintain, because I cannot properly work on that MessageFormat
file, since its real versions are excluded from IDE (and they contain "wrong package", so, even if I include it into IDE, then everything is broken and if I fix the package, then it doesn't compile, because maven just replaces the file, without package changing). Also, if I work on the "current" MessageFormat
it then gets overridden by Maven. I also tried to put the format into Maven modules and include them on the particular profile, but it means I must delete the format from the base module and break all the links to it, which makes maintenance even worse.