Skip to content

Add modular API family artifacts - #2027

Open
araiprof wants to merge 1 commit into
Adyen:mainfrom
araiprof:feature/modular-api-artifacts
Open

Add modular API family artifacts#2027
araiprof wants to merge 1 commit into
Adyen:mainfrom
araiprof:feature/modular-api-artifacts

Conversation

@araiprof

Copy link
Copy Markdown

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, and webhook-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)
  • Added ModularArtifactsTest, which verifies every compiled com.adyen class belongs to exactly one classifier and detects missing or overlapping package patterns
  • Built a downstream multi-module Gradle/Spring Boot application using only the required classifiers (:web-app:build)
  • Verified each classifier against core with jdeps; no unresolved internal Adyen class references

Fixed issue: N/A

@araiprof
araiprof requested a review from a team as a code owner August 10, 2026 18:39

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread pom.xml
Comment on lines +90 to +98
<executions>
<execution>
<id>core-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>core</classifier>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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:

  1. No Transitive Dependency Reduction: Since all classifiers share the same pom.xml, a consumer depending on a lightweight classifier (like webhooks) will still transitively inherit all dependencies of the entire library (such as httpclient5, commons-codec, etc.). This defeats a key benefit of modularization (reducing classpath pollution and dependency conflicts).
  2. Missing Classified Sources and Javadocs: Currently, maven-source-plugin and maven-javadoc-plugin only build the default, unclassified sources/javadocs. When developers use a classifier like checkout, their IDEs (IntelliJ/Eclipse) will attempt to download adyen-java-api-library-43.0.0-checkout-sources.jar and fail, leaving them without source code navigation or Javadoc tooltips.
  3. Manual Dependency Management: Consumers must manually declare both core and 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"))) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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)) {

Comment on lines +112 to +129
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();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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:

  1. Update patterns to return List<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;
}
  1. Update JarPatterns to hold and match PathMatchers:
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));
  }
}
  1. Completely remove the toRegex method.

This simplifies the codebase, improves maintainability, and leverages robust, standard JDK APIs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant