Add modular API family artifacts - #2027
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces modular artifacts to the library by configuring the maven-jar-plugin in pom.xml to build classified JARs (such as core, checkout, payments, etc.) and documenting their usage in the README.md. It also adds a test (ModularArtifactsTest) to ensure all compiled classes are correctly partitioned without duplication or omission. Feedback on these changes suggests addressing limitations of using Maven classifiers (such as lack of transitive dependency reduction and missing sources/javadocs) by considering a multi-module structure or configuring source/javadoc plugins. Additionally, the test can be improved by verifying the existence of the target classes directory before walking it to prevent raw exceptions, and by replacing the custom glob-to-regex converter with Java's standard PathMatcher API.
| <executions> | ||
| <execution> | ||
| <id>core-jar</id> | ||
| <phase>package</phase> | ||
| <goals> | ||
| <goal>jar</goal> | ||
| </goals> | ||
| <configuration> | ||
| <classifier>core</classifier> |
There was a problem hiding this comment.
While using Maven classifiers to partition a single JAR is a clever way to avoid restructuring the source code, it introduces several significant limitations that impact developer experience and dependency management:
- No Transitive Dependency Reduction: Since all classifiers share the same
pom.xml, a consumer depending on a lightweight classifier (likewebhooks) will still transitively inherit all dependencies of the entire library (such ashttpclient5,commons-codec, etc.). This defeats a key benefit of modularization (reducing classpath pollution and dependency conflicts). - Missing Classified Sources and Javadocs: Currently,
maven-source-pluginandmaven-javadoc-pluginonly build the default, unclassified sources/javadocs. When developers use a classifier likecheckout, their IDEs (IntelliJ/Eclipse) will attempt to downloadadyen-java-api-library-43.0.0-checkout-sources.jarand fail, leaving them without source code navigation or Javadoc tooltips. - Manual Dependency Management: Consumers must manually declare both
coreand the specific family classifier (e.g.,checkout), which is verbose and error-prone.
Recommendation
The ideal solution is to transition to a standard Maven multi-module project (e.g., submodules for core, checkout, etc.).
If that is out of scope for this PR, you should at least configure the maven-source-plugin and maven-javadoc-plugin to generate matching classified JARs for each execution so that IDE integration is not broken.
| jarPatterns.keySet()); | ||
|
|
||
| Map<String, List<String>> ownersByClass = new HashMap<>(); | ||
| try (Stream<Path> compiledClasses = Files.walk(Path.of("target/classes/com/adyen"))) { |
There was a problem hiding this comment.
If the test is run in an environment where the project hasn't been compiled yet (or from an IDE with a different output directory configuration), Files.walk will throw a raw NoSuchFileException.
Consider checking if the directory exists first and throwing a descriptive error message to improve the developer experience.
Path classesDir = Path.of("target/classes/com/adyen");
if (!Files.exists(classesDir)) {
throw new IllegalStateException("Compiled classes directory '" + classesDir + "' does not exist. Please run 'mvn compile' first.");
}
try (Stream<Path> compiledClasses = Files.walk(classesDir)) {| private static String toRegex(String glob) { | ||
| StringBuilder regex = new StringBuilder("^"); | ||
| for (int index = 0; index < glob.length(); index++) { | ||
| char character = glob.charAt(index); | ||
| if (character == '*') { | ||
| boolean doubleWildcard = index + 1 < glob.length() && glob.charAt(index + 1) == '*'; | ||
| regex.append(doubleWildcard ? ".*" : "[^/]*"); | ||
| if (doubleWildcard) { | ||
| index++; | ||
| } | ||
| } else if ("\\.[]{}()+-^$|".indexOf(character) >= 0) { | ||
| regex.append('\\').append(character); | ||
| } else { | ||
| regex.append(character); | ||
| } | ||
| } | ||
| return regex.append('$').toString(); | ||
| } |
There was a problem hiding this comment.
Instead of implementing a custom glob-to-regex converter (toRegex), which can be error-prone and hard to maintain, you can leverage Java's standard java.nio.file.PathMatcher via FileSystems.getDefault().getPathMatcher("glob:" + glob).
Here is how you can refactor the test to use standard Java APIs:
- Update
patternsto returnList<PathMatcher>:
private static List<PathMatcher> patterns(Element execution, String tagName) {
NodeList nodes = execution.getElementsByTagName(tagName);
List<PathMatcher> matchers = new ArrayList<>();
FileSystem fs = FileSystems.getDefault();
for (int index = 0; index < nodes.getLength(); index++) {
matchers.add(fs.getPathMatcher("glob:" + nodes.item(index).getTextContent().trim()));
}
return matchers;
}- Update
JarPatternsto hold and matchPathMatchers:
private static final class JarPatterns {
private final List<PathMatcher> includes;
private final List<PathMatcher> excludes;
private JarPatterns(List<PathMatcher> includes, List<PathMatcher> excludes) {
this.includes = includes;
this.excludes = excludes;
}
private boolean matches(String className) {
Path path = Path.of(className);
return includes.stream().anyMatch(matcher -> matcher.matches(path))
&& excludes.stream().noneMatch(matcher -> matcher.matches(path));
}
}- Completely remove the
toRegexmethod.
This simplifies the codebase, improves maintainability, and leverages robust, standard JDK APIs.
Description
The library is currently published as a single JAR containing every API, model, webhook, and Terminal API class. This adds opt-in classifier JARs for
core,checkout,platforms,payments,terminal,classic-platforms,webhooks, andwebhook-handlers, while keeping the existing unclassified artifact unchanged and backward-compatible.The classifiers partition the existing compiled output at package boundaries, so this does not move or modify generated sources. README examples show how Maven and Gradle consumers can select only the families they use.
A representative Spring Boot application using Checkout, Platforms, Terminal, and selected webhook models reduced its executable JAR by 1,281,151 bytes (1.22 MiB). Applications using fewer API families can avoid a larger portion of the current all-in-one JAR.
Tested scenarios
mvn clean verify -Dgpg.skip=true(618 tests, 0 failures, 4 skipped)ModularArtifactsTest, which verifies every compiledcom.adyenclass belongs to exactly one classifier and detects missing or overlapping package patterns:web-app:build)corewithjdeps; no unresolved internal Adyen class referencesFixed issue: N/A