maven - separate modules for interfaces and implementation with Spring - java

We are working on Mavenizing our java project and we would like to setup a clean separation between interfaces and implementations for each module.
In order to do so, we want to split each module into two sub-modules one for interfaces and data objects used by them and another for implementations.
For example:
+commons
+commons-api
+commons-impl
The POMs of the modules will be configured such that no module depends on the impl sub-modules. This way no code from one module will be able to "see" implementation details of another module.
What we are having trouble with, is where to put our spring XMLs.
In our project we automatically import spring XML files using wildcard import like
<import resource="classpath*:**/*-beans.xml"/>
This way the location of Spring XMLs doesn't really matter at runtime, as all the modules get loaded into the same class loader and, the strict one way dependency rules in the POMs don't apply.
However, during development we want the IDE - we use Intellij IDEA - to recognize implementation classes referenced from the spring XMLs.
We also want IDEA to recognize beans defined in other modules.
If we put the spring XMLs in API sub-modules - they won't "see" the implementation classes in the impl sub-modules.
If we put them in the impl sub-modules, their beans won't be "seen" from other modules.
It is probably possible to configure the IDEA project to recognize spring XMLs from modules on which there is no dependency, but we prefer for our POMs to hold all the project structure information and not rely on IDEA project files.
We considered creating a third sub-module just to hold Spring XMLs (and perhaps hibernate xmls as well). For example:
+commons
+commons-api
+commons-impl
+commons-config
The external modules will depend on both commons-api and commons-config and commons-config will depend on both commons-api and commons-impl, with the dependency on commons-impl marked as "provided" (to prevent transitive resolution).
This however seems like a complex and awkward solution and we feel that there must be a better - simpler way to achieve interface/impl separation with Maven and Spring.

What you need is a runtime dependency scope:
runtime - This scope indicates that the dependency is not required for compilation, but is for execution. It is in the runtime and test classpaths, but not the compile classpath.
(https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)
Define a runtime dependency from one impl module to another impl module where you use the impl classes in the *-beans.xml config. Intellij will correctly recognize this in spring configuration files, but won't auto complete them in code (but it will do that in test code).
Also if anyone used the classes in the code, compilation through maven would fail, because the runtime dependency is not on a compile class path.

You can achieve decoupling of api and impl like this:
+ commons (pom)
+ pom.xml <--- serves as a parent aggregator (see below)
+ commons-api (jar) <--- contains models, interfaces and abstract classes only
+ commons-impl (jar) <--- depends on commons-api
+ commons-config (jar) <--- depends on commons-impl only (no need to depend on commons-api as it is brought in transitively)
+ external-project (war or jar) <--- has commons-config as a dependency
Parent aggregator pom (specify build order):
<modules>
<module>commons-api</module>
<module>commons-impl</module>
<module>commons-config</module>
</modules>
The config module can be omitted if it only contains spring application context configuration. The app configuration xml should be in the classpath and folder structure of the module that contains the artifact that you are deploying. So if you are building a war artifact, the app context should be in there.
The only configuration that should be in your commons module would be in a test package of your impl module.

In short you want Idea to override maven dependency graph but avoid keeping this configuration in idea project files?
One option is to group implementation dependencies in a maven profile. This profile would not be enabled by default but you should be able to mark it as active under idea.

Two ideas come to mind:
You will have one (or more) modules where all the modules (api+impl) are dependencies, you could place your spring configuration files there.
Place the spring configuration files in the api modules and declare a dependency on the impl module with scope provided this way the implementations will be known, while there is no dependency of the api for the deployment.

commons-impl at runtime scope in external modules
commons (pom dependencyManagement) =>
+commons-api (compile)
+commons-impl (compile)
+commons-config (compile)
commons-impl (pom dependencies) =>
+commons-api (compile)
+commons-config (compile)
external modules (pom dependencies) =>
+commons-impl (runtime)
+commons-api (compile)
+commons-config (compile)

keep modules number as little as possible;
This speeds up project build time and simplifies its layout.
keep modules structure as plain as possible: single root + all sub modules in the same folder, e. g.:
pom.xml
commons-api/
commons-runtime/
module-a-api/
module-a-runtime/
...
This simplifies navigation across the project, when modules number is really high (>50)
provide runtime-scoped dependencies to the runtime modules only when they are required;
This keeps your architecture clear. Use mocks instead of explicit dependency to another runtime module.
keep your api spring contexts in api modules, define your public beans as abstract bean + interface;
keep your implementation contexts in runtime modules, override api beans with your implementations via spring profiles (use <beans profile="default").
Result: simple, transparent layout and design; full ide support; no explicit dependencies on runtime module internals.

Related

Gradle inject root project class path to dependencies

I am building a library, which has separate entry points (like server, gui), with different dependencies. Each entry point is in a separate sub project with its own dependencies.
In the root project, where I start the build from, I want to select the entry point, and only build with that dependencies. That is working.
But I want to instantiate a class (eg MainClass) of the root project from the library entry point and I cant add the root projects class path to the dependency. (Diagram)
Root projects build file looks like this now:
dependencies{
implementation project(':server')
}
It seems to me that it would be easier to understand and clearer if the entry point projects depended on the core API instead of vice versa.
You could have project structure like:
settings.gradle
core/
build.gradle
src/
server/
build.gradle
src/
gui
build.gradle
src/
server and gui project build.gradle files should contain:
dependencies {
implementation project(':core')
}
The project that uses the library could depend on Server and/or GUI projects and instantiate the necessary class (ServerEntry or GuiEntry) directly.
If you want to be able to switch between different entry point implementations without changing the code in the project that uses the entry point instance I'd suggest using a dependency injection framework (Spring, Guice, Dagger). Dependency injection would help to separate configuration (binding interfaces to classes) from the actual application.
I solved my problem with Composite build. I added includeBuild '../path-to-lib' in the settings.gradle, Than I created a subproject Project to the library with the proper package and class name. On run it throws an error, that the developer should create this class.
Its also important to add all the subprojects to the same group:
allprojects{
group = 'library-group'
}
In the host project, I can depend on the library:
dependencies{
implementation module('library-group:suproject')
}
Now Gradle automatically overwrites the Classes on the same route as the documentation suggest, and I can finally inject my host project into the lib, and compile it as a whole.

How do optional compile-time dependencies work?

For the longest time, I thought that in Java you either had one of two types of dependencies:
Required compile-time dependencies (dependencies always required at compile time)
Possibly optional runtime dependencies (dependency that can be
resolved at runtime)
Recently, I found out that compile dependencies can be optional too. For example, commons-beanutils is listed as an optional compile dependency of JXPath.
How can this work? Can a dependency really be used at the time of compilation yet remain fully optional?
EDIT: I might have been unclear. I'm looking for a case where a dependency is used at compile-time and is at the same time fully optional, or an explanation why such a dependency is impossible.
A class can compile to an interface but the implementation of that interface is not needed during compilation. The implementation is needed during runtime.
Example commons-logging, JPA, JDBC etc which are frameworks, an application can compile based on these. At runtime an implementation is needed to execute the code. Sample implementations - Common Bean utils, Oracle thin driver, Eclipse link etc.
An extensive quote from Maven documentation describes this quite clearly:
Optional dependencies are used when it's not possible (for whatever reason) to split a project into sub-modules. The idea is that some of the dependencies are only used for certain features in the project and will not be needed if that feature isn't used. Ideally, such a feature would be split into a sub-module that depends on the core functionality project. This new subproject would have only non-optional dependencies, since you'd need them all if you decided to use the subproject's functionality.
However, since the project cannot be split up (again, for whatever reason), these dependencies are declared optional. If a user wants to use functionality related to an optional dependency, they have to redeclare that optional dependency in their own project. This is not the clearest way to handle this situation, but both optional dependencies and dependency exclusions are stop-gap solutions.
Why use optional dependencies?
Optional dependencies save space and memory. They prevent problematic jars that violate a license agreement or cause classpath issues from being bundled into a WAR, EAR, fat jar, or the like.
How do optional dependencies work?
Project-A -> Project-B
The diagram above says that Project-A depends on Project-B. When A declares B as an optional dependency in its POM, this relationship remains unchanged. It's just like a normal build where Project-B will be added in Project-A's classpath.
Project-X -> Project-A
When another project (Project-X) declares Project-A as a dependency in its POM, the optional nature of the dependency takes effect. Project-B is not included in the classpath of Project-X. You need to declare it directly in the POM of Project X for B to be included in X's classpath.
A practical example: imagine that you are a developer of a library/framework SuperLib that is built as one superlib.jar. Your library provides multiple features. Its main feature (that most of the users use) is dependency injection based on a third-party di library. However, one of your classes - EmailApi - offers features to send e-mails, using a third-party email library. Since superlib is one artifact, it needs both di and email to be compiled.
Now put yourself in the position of a user who uses superlib. They are interested in the dependency injection features. This is the core role of your library, so the dependency between superlib and di would not be optional.
However, most users are not interested in sending emails and may be bothered by having a useless email library and its dependencies added to their application (which will cause size increase of their application and may cause a dependency version clash between the dependencies of email and dependencies of the user's application). Therefore, you would mark the dependency on email as optional. As long as the user does not use your EmailApi class, everything will run fine. However, if they do use EmailApi, they will need the email dependency, otherwise the application will fail at runtime with ClassNotFoundException for whichever class from email would be referenced in EmailApi. The user of your library will need to add the email dependency explicitly in their POM.
See also When to use <optional>true</optional> and when to use <scope>provided</scope>.
What you described is actually a feature of Maven, the build tool, but not Java itself.
Without build tools, using just 'javac' you need to specify all classes or interfaces that directly used in your code. Sure there are options for dynamic class loading and even runtime compilation, but thats not on topic.
One of use-cases with separation on interface and implementation is described in previous answer, another popular case is based on classpath scanning:
if some specific class is present in classpath and/or has specific annotation - an optional module will be loaded.
That's how Spring Boot modules are loaded.

Maven modules and Spring test resources

I have a Maven project made up of several modules. Some of the modules depend on the other modules for example Module C <- Module B <- Module A. Module C depends on Module B which depends on Module A.
In each module, I have Spring config files in main/resources and test/resources, those under test are for unit testing, while the those under main are for release/production. Each config file is self contained - Module B contained only its Spring config (file names are like so foo-B.xml, foo-A.xml)
However, when I need to test Module C, I need to reference Module B's Spring config under test/resources, but what is included is Module B's main/resources config file. This presents a problem because the production file has references to JNDI datasources where test one does not.
How can I get Maven or Spring to reference the test configuration file from the module dependency?
Maven separates the source classes & resources from the test classes & resources. You may configure Module B to create a test jar using the maven-jar-plugin test-jar goal. Then, you may have Module C reference Module B's test code as a dependency.
<dependency>
<groupId>com.myCompany</groupId>
<artifactId>moduleB</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
Alternately, you can create a regular Maven project including only the test code you'd like to share, then include that as a test dependency where needed. This idea is described in the maven jar plugin's usage docs.
I'm not sure this can be done. Maven deliberately does not include test resources in artifacts. If I were in your place, I would duplicate the test resources in module C. Presumably, you're not testing the same things in both modules, so hopefully it won't cause a bad case of copy&paste/dual-maintenance.
As an aside, I try to avoid having "production" data sources and "test" data sources. Use the same JNDI name for both, but have the JNDI provider configured to point to test or production based on the circumstance. For example, all of our web servers have the same data sources defined, but the JDBC urls are different for dev/qa/prod. For your unit tests, use something like simple-jndi to simulate a JNDI environment.

component-scan classpath subdirectory (a'la plugins + annotation capable)

Goal:
List item
I have a spring webapp project, which is using component scan for configuration and autowiring interface implementation through maven submodules
the main project is dependant on the other modules thus the jar's of the submodules are placed in the /WEB-INF/lib folder
the submodules (aka plugins) have common package parent name x.y.z.extension eg. x.y.z.extension.pluginA
the classes in this package are annotated with #Component or #Configuration
in the servlet xml configuration i have placed such component-scan information:
Code:
<context:spring-configured />
<context:component-scan base-package="x.y.z.extension" />
With the mentioned configuration everything is working correctly.
What I would like to achieve:
List item
remove dependency of the main webapp maven module and other modules - the core webapp will be shipped without plugins
create subfolder e.g. "WEB-INF/classes/plugins" in the classpath classes dir
put there the mentioned jar's from submodules (or extract the jar content to eg. WEB-INF/classes/plugins/pluginA) - this could be done during "plugin installation" with webapp restart after new plugin installation
spring automagically should detect annotated classes and load it into the application context (and use not annotated classes in the plugin jar (annotated classes are mainly interface implementation but they are using some not annotated classes in the jar))
and ... of course this does not work The classes are not found.
If it possible to achieve this only using spring ecosystem, or should I take a look into other examples e.g. jspf ?
How can I modify classpath scanning with spring and also keep automatic component scanning ?
thanks !

Why does a dependency with scope "provided" hide transitive dependencies in Maven?

I have three modules in my Maven project (this is slightly simplified):
model contains JPA annotated entity classes
persistence instantiates an EntityManager and calls methods on it
application creates instances of the classes in model, sets some values and passes them to persistence
model and persistence obviously depend on javax.persistence, but application shouldn't, I think.
The javax.persistence dependency is moved to a top-level POM's dependencyManagement section because it occurs in a number of submodules where I only reference that entry.
What's surprising to me is that I have to reference the dependency in application when I set its scope to provided, whereas I don't have to when its scope is compile.
With a scope of provided, if I don't list it in the dependencies for application, the build fails with an error message from javac:
com.sun.tools.javac.code.Symbol$CompletionFailure: class file for javax.persistence.InheritanceType not found
What's going on?
model and persistence obviously depend on javax.persistence, but application shouldn't, I think.
That's true. But transitive dependencies resolution has nothing to do with your problem (and actually, javax.persistence is provided to model and persistence on which application depends with a compile scope so it's omitted as documented in 3.4.4. Transitive Dependencies).
In my opinion, you are victim of this bug: http://bugs.sun.com/view_bug.do?bug_id=6550655
I have the same issues with an EJB3
entity that uses the Inheritance annotation:
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
A client class using this entity won't
compile when the ejb3 annatations are
not on the classpath, but crash with
the following message:
com.sun.tools.javac.code.Symbol$CompletionFailure:
class file for
javax.persistence.InheritanceType not
found
[...]
Note that is a special case of bug 6365854 (that is reported to be fixed); the problem here seems to be that the annotation is using an enum as its value.
The current workaround is to add the missing enum to the CLASSPATH.
In your case, the "less worse" way to do that would be to add javax.persistence as provided dependency to the application module. But that's a workaround to the JVM bug, application shouldn't need that dependency to compile.
umm, because provided dependencies are not transitive? that's builtin behavior for maven.
The dependencyManagement section declares what dependencies will look like if you use them, not that you will use them. So you still need to declare a minimal dependency declaration to have the configuration applied in your child project. See the dependency management section of the Maven book for details.
The minimum required is typically the groupId and the artifactId.
If you want to inherit the configuration without declaring it at all, you should define it in the parent's dependencies section rather than dependencyManagement

Categories

Resources