Help coderanch get a
new server
by contributing to the fundraiser
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

Using Spring in a Library

 
Ranch Hand
Posts: 1419
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I have been tasked with doing a small piece of maintenance to a system that I think was poorly designed (but there's nothing I can do about that, now).

It's a stable system that is rarely modified.

The overall system consists of several Spring Boot webservice projects that share common code via a dependency on a Nexus library. The library is not an executable, nor is it a Spring Boot project (nor is it Spring of any kind).  It's just a Maven project that produces a Jar.  The library merely defines a huge, complex class that can be instantiated by projects that depend on this library, so they can call the methods on the instantiated object.

Within this library is a private method that directly reads an external system's RDBMS.  This private method is called by several of the public library's methods, which in turn are called by several of the Spring Boot projects that depend on this library.

My assignment is to modify that method, replacing the direct reading of the external RDBMS with a call to the external system's new restful webservice.

My experience calling restful webservices from within old Java systems was a hair-pulling experience getting things to work, and figuring out how to assemble and use the libraries that handle things like HTTP connections and conversion to and from JSON.

I have been greatly impressed by descriptions of restful webservice consumption from within Spring Boot projects using RestTemplate -- the ease of configuration, the small number of steps needed, and the automatic conversion of results from JSON to a structurally analogous Java object.

However, this library that I must modify is NOT a SpringBoot project.  And indeed, every tutorial I have read about Spring and Spring Boot seems to focus on creating executables, particularly web applications that respond to HTTP calls either from web pages or from other applications.

Is there a simple way that I can easily call a restful web service from within this library, much like I could in a Spring Web project?  That is to say, without fundamentally changing the way this library jar is imported in Eclipse, built, deployed and consumed -- merely by adding a few maven dependencies to what I now have?
 
Saloon Keeper
Posts: 27933
198
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
There's really no difference between any of the standard libraries you can find in the Maven repositories and a private library that's defined wholly for private use within your business. A Nexus Maven repo can cache stuff from the master Maven repos, but it can also serve as the target repo for an in-house "maven deploy" operation. Any Maven project won't be able to tell whether it's a public Maven library artefact or your own.

In fact, I had a project where I had several of my own libraries for database interfacing using Spring JPA and that's exactly how I handled them. As in your case, these libraries were true code collections, not executable independently.

When you create a library artefact in Maven and deploy it to a Maven archive such as Nexus (or even your local machine), Maven's deployment mechanism not only publishes the JAR file, it also publishes a POM. That POM indicates the dependencies that your JAR requires in order to be usable. This is part of Maven's transitive dependency mechanism. If I create a library that uses Spring JPA, but I never invoke Spring JPA in the application that uses the library, then a maven build of the application would simply need to list my library as a dependency and Spring JPA would be pulled in automatically. Switch that library to use ReST and say, an Apache HTTP jar, and instead the application build will pull in those dependencies from my library and the application's POM wouldn't change at all (except for the version number of my library, of course!).

Note that since the transitive POM is, indeed a Maven POM, it not only pulls in upstream dependencies, it specifies what version (or version range) of each upstream dependency to transitively include. That ensures that you'll only be building with library versions confirmed to work with your library, and that - along with the whole automatic fetch thing - is where Maven gets much of its power.
 
Sheriff
Posts: 22791
131
Eclipse IDE Spring Chrome Java Windows
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Tim Holloway wrote:Note that since the transitive POM is, indeed a Maven POM, it not only pulls in upstream dependencies, it specifies what version (or version range) of each upstream dependency to transitively include. That ensures that you'll only be building with library versions confirmed to work with your library, and that - along with the whole automatic fetch thing - is where Maven gets much of its power.


I've experienced this to be a flaw of Maven as well. If you have a library that depends on Spring version x, and you let it managed those dependencies for you, your application needs to use Spring version x as well. Otherwise you may end up with an application that uses Spring Boot 2.7.x but a library that provides a Spring JPA compatible with Spring Boot 2.6.x. That could very well lead to nasty runtime errors.

I've found three ways of solving that issue:
1) include the same dependencies in the application as well but with a version that's compatible with the application's framework version. This version then overrides the version provided by the library (direct dependencies "beat" transitive dependencies).
2) mark the dependencies as provided in the library, and include them in the application.
3) release a new version of the library whenever there's a framework version update.

For both of the first two options you need to define the dependencies in your application. The first option may or may not work if you forget them, but you have a time bomb, with a NoSuchMethodError or NoClassDefFoundError possibly being thrown only after deploying to production. The second option will not work if you forget the dependencies. The third option has the same possible risk as the first option if you forget to update its version.

Out of these options, I have gone for option 1 a lot for my own Spring Boot libraries, but mostly because the dependencies I do have are either always present (autoconfigure, Spring Boot itself), or are marked as optional (my code can live without the dependencies). I dislike option 3 because, at Spring Boot's rate, I'd have to release a new version at least once a month.

At work, I've gone for option 2 for the Quarkus project I'm currently working on. The additional dependencies are well documented, and we can upgrade Quarkus without having to upgrade and release every library (but we do test those against the newer versions).
 
Tim Holloway
Saloon Keeper
Posts: 27933
198
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
What you're describing is Java's version of "DLL Hell".

In actuality, library conflicts are part of Maven's overall guaranteed reproducibility and operability. Usually, a newer version of a library is more or less upwards-compatible with older ones. But I've seen some roaring exceptions. Maven provides a guard rail against accidental breakage, and yes, it can be a royal pain to find a "sweet spot" set of libraries when you upgrade a complex project. But it's another case where the pain occurs in development and not in production.

Personally, I hate production disasters. Were it not so, I'd have dropped Java for Python or JavaScript ages ago. And speaking of JavaScript: my NodeJS server broke because NPM was upgraded and it hated the OS-supplied Node version, I had to hack in an override for Node, which broke packages in the application (notably MySQL!) and I was in a really evil mood for a while there.
 
Rob Spoor
Sheriff
Posts: 22791
131
Eclipse IDE Spring Chrome Java Windows
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Tim Holloway wrote:Usually, a newer version of a library is more or less upwards-compatible with older ones. But I've seen some roaring exceptions.


Apart from the incidental library that doesn't know how to use semver (I'm looking at you, Bouncy Castle!), most times I've gotten errors because multiple modules of the same framework (mostly Spring and Jackson) don't have the same version. That's because these versions are often released together, and may refer to newer methods / classes, or internal methods / classes that have been removed in newer versions.

Personally, I hate production disasters. Were it not so, I'd have dropped Java for Python or JavaScript ages ago. And speaking of JavaScript: my NodeJS server broke because NPM was upgraded and it hated the OS-supplied Node version, I had to hack in an override for Node, which broke packages in the application (notably MySQL!) and I was in a really evil mood for a while there.


Wow, I understand the evil mood there. Upgrade NPM: the OS complains. Don't upgrade NPM: the application complains.
 
Tim Holloway
Saloon Keeper
Posts: 27933
198
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Major product lines mostly advance version numbers across the line. So the easiest way to make everything happy is to determine which version satisfies the needs of all modules (which often means upgrading or retarding other components), then define that version as a symbolic variable in the POM. I do this for Spring, for Hibernate, and so forth. That way I only have one place that needs to be updated.
 
Frank Silbermann
Ranch Hand
Posts: 1419
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
OK, so I get that there is a danger with conflicting dependencies.

Does that mean that to call a restful webservice from within our Nexus library I should give it jars which have Spring's version of Jackson and Jax_rs?
If so:

How do I find which versions were included -- do I track through Maven files?
Where do I find instructions on using them outside the context of Spring?

Or does that mean I should try to avoid conflict by using completely different implementations of Jax_rs and JSON processing?
If so:

What do you suggest I use?
 
Tim Holloway
Saloon Keeper
Posts: 27933
198
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Spring does not independently implement stuff like Jackson. They don't re-invent the wheel. If there's a "Spring Jackson", then it is going to provide enhancements that make using Jackson more Spring-friendly.

Maven has a way to list the version requirements for dependencies and how it resolves conflicts (mvn dependency:list/mvn dependency:tree). Or, if you prefer, many IDEs have some sort of way to displaying them graphically.
 
Bartender
Posts: 2438
13
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi,
I saw Rob Spoor mentioned

mark the dependencies as provided in the library

.
Does it mean something like this?
<groupId>...dependency group id</groupId>
<artifactId>... depdendency artifact id ...</artifactId>
<scope>provided</scope>

What is the purpose of setting the scope ?  I think as along as the dependency is imported, code can compile.
I know some people use compile , test , provided, runtime for the scope. Why do we need the scope ?

 
Tim Holloway
Saloon Keeper
Posts: 27933
198
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Himai Minh wrote:
What is the purpose of setting the scope ?  I think as along as the dependency is imported, code can compile.
I know some people use compile , test , provided, runtime for the scope. Why do we need the scope ?


Scope determines when the dependency is used,

Default is compile scope. A dependency with compile scope is referenced when the artefact is compiled, but also included in the target artefact (JAR, WAR, whatever).
test means that the dependency is only required for testing. For example, a mock object library.
provided means that the dependency will not be included in the target artefact because the target environment provides the runtime version of that dependency, For example, the servlet-implementation library in Tomcat. You should never include that in a WAR because it's in Tomcat's /lib directory already.

I forget the details on runtime, but I think Rob may use it a lot.
 
Rob Spoor
Sheriff
Posts: 22791
131
Eclipse IDE Spring Chrome Java Windows
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Tim's descriptions of the above are correct. There's also system which you shouldn't really use, and runtime. That's the complete opposite of provided - it gets included in the target artefact, but you can't use it to compile against. I've only used it for database drivers, where I use JPA, JDBC, or another API to write the code that then should work independently of the database driver. By using scope runtime you don't run into the risk of accidentally using a Postgres / Oracle / ... specific class which won't exist if you switch to a different database.
 
What? What, what, what? What what tiny ad:
We need your help - Coderanch server fundraiser
https://coderanch.com/t/782867/Coderanch-server-fundraiser
reply
    Bookmark Topic Watch Topic
  • New Topic