Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for creating lightweight clients without a dependency on spring core components. #1926

Open
dnavre opened this issue Mar 15, 2023 · 2 comments

Comments

@dnavre
Copy link

dnavre commented Mar 15, 2023

I have a spring hateoas API which is being called from non-spring environments where memory usage and even artifact size are of a concern to me. To reduce code duplication I have obviously separated the models/dtos returned by my API into a separate maven module and use this module both in the API and in the non-spring client library. The problem is that the way spring-hateoas is designed in encourages(or maybe requires?) me to extend org.springframework.hateoas.RepresentationModel in my models which is in the main Gradle: org.springframework.hateoas:spring-hateoas dependency. If I add that dependency to my common code it brings with itself all kinds of other spring dependencies. The result is a blown out of proportions client library that has lots of dependencies on spring components.

This is an issue for many cases, including using the client library in projects that:

  1. Are using older versions of spring (conflicts on spring dependency versions)
  2. Are delivered as a shaded jar (the jar becomes too big)
  3. Do not have a dependency on spring (spring becomes a dependency and may even cause conflicts with other 3rd party non-spring libraries, such as logging frameworks, jsonpath library, etc.)

It would be great, if the current hateoas project was split into two modules. One to be used by the API project with all the dependencies to spring's core components and the other without any dependencies whatsoever and just containing the parent classes for API models.

I searched a lot for a solution for this problem but didn't find anything.Have I missed something? Also, as a workaround the only way I see is to copy-paste the the API model parent classes into the API model containing project. However this is a really ugly solution and may even pose legal/licensing issues.

@odrotbohm odrotbohm self-assigned this Mar 16, 2023
@odrotbohm
Copy link
Member

Thanks for bringing this up. The reason that we haven't explored this in greater detail is that, for one, Spring HATEOAS primary environment is indeed Spring Boot applications and, for the other, that not too many people were asking for this before, we currently don't have a good feel for where to draw the lines and a potential split up also comes with a couple of downsides. Let me elaborate:

  1. We actually don't want to develop an API that has "no dependencies whatsoever" as that would mean that we would have to reinvent the wheel in many places. There's no point in us introducing yet another enum to capture HTTP statuses just to avoid depending on spring-web. Not only because would like to avoid maintaining duplicate code, but also because it would be inconvenient for our users to deal with two different but similar abstractions in their controller implementations. It's Spring HATEOAS after all. What I am trying to get to is that even the top level hateoas package has dependencies to spring-core and spring-web and I consider this a feature, not a bug. So even a potential spring-hateoas-api would have to depend on those.
  2. As you spoke about client libraries, I wonder which use cases exactly we would like to tailor the artifact arrangement for. Spring HATEOAS' client package contains an interface LinkDiscoverer that you're likely to depend on when building hypermedia-aware clients. That needs a reference to spring-plugin to be able to hide different implementations behind that interface depending on the media type detected in a response. So, even if we went with a split of api, core, client, server, the individual artifacts would still at least require some dependencies on Spring. And which ones these are highly depends on which APIs you use. Trying to come to the most reduced set of dependencies for particular use cases leads us to…
  3. A potential split-up creates a new problem: users having to assemble the right set of dependencies for their particular use case. This is obviously at odds with the expectation of things to just work when someone decides "I want to use Spring HATEOAS". We have actually had requests to make some dependencies that were declared as optional before as non-optional because it's hard to communicate "You need additional dependency X to be able to use feature Y". Yes, we can put this into the docs, but we all know that hardly anyone reads those and trying to use a feature and running into a ClassNotFoundError.
  4. If you really want to avoid Spring HATEOAS imposed dependencies into a library using it, you might want to start with excluding the ones you don't need and find out whether your arrangement still works. That's not the most convenient way, I know, but assuming that very isolated consumption of Spring HATEOAS is more the exception than the norm, it might still be a good tradeoff after all.

Now that I have outlined all the challenges, let me make sure that I am still not opposing the idea to exploring other arrangements. We essentially need to find a good way to come to a new tradeoff, that still is a good balance for all parties involved.

I wonder if you can elaborate on what APIs exactly your client libraries use to be able to vet what different dependency arrangement a potential artifact arrangement would result in and whether you'd consider this an improvement over the current situation?

@dnavre
Copy link
Author

dnavre commented Mar 29, 2023

For some context, I am/was planning to use the client library in a big data processing application such as Apache Spark/Flink. The thing with these platforms is that one is already forced to use shaded jars and having all the Spring dependencies also sitting inside is an inconvenience. Moreover, in our example we're forced to use the client library in Java_11 context which the current structure of the hateoas project very efficiently makes impossible :)

Our API is quite simple, it doesn't have many objects linking to each other and overall things are not far from a regular REST API. Regardless, we ended up generating the client library from scratch using the OAS v3 spec, then we had to do some manual tweaking to make the generated code actually work. Of course this is a terrible solution, the generated classes are lacking in many ways compared to the DTO layer we have already created for the API, future re-generations of the client will also involve additional manual work to make it workable. But this was the only reasonable way to make it work we could think of.

As for the points you raised:

  1. I don't see why this is the case. In all my previous work I was always able to decouple my data transfer layer(API interfaces + JSON/XML objects & enums transferred over the network) from the server and client code. After all HATEOAS is a convention/standard that exists not only in the context of Spring framework but also outside of it. Tomorrow, one may decide to use the HATEOAS API as a regular REST API or another java HATEOAS client implementation may be available and a user may decide to use that to build their client library. The client library may have to run on embedded platforms, obsolete versions of Java and generally have very different requirements compared to how the server part of the API is implemented. I don't see why in any of those cases a user of the API should be forced to have the whole spring framework on their classpath, or use the version of Java that spring depends on for things as basic as the HTTP status, which if we dumb things down a little bit, is actually just an integer value.
  2. This I honestly can't answer. I'm a relatively new user of HATEOAS and don't know it's inner workings that well. As a cautious idea, maybe wrapping the plugin registry in a wrapper and then making it an SPI may lead to a solution. I personally, wouldn't mind providing this mapping manually in the client's code.
  3. Alas, things nearly never just work out of the box, once they do I'll be out of my job. :) Spring & Spring Boot have of course tremendously simplified "just running things" but I think an average java developer is still very much used to carefully tweaking their dependencies to achieve the result they a looking for. And this example, I think, is similar to having slf4j-api and logback, or hibernate and jpa on the classpath, people should be used to working with this kind of things.
  4. We sadly couldn't do this because of different Java version requirements.

I'm sorry for the really long post. I also understand that achieving this with HATEOAS is more complex than with REST or even GraphQL.

I also want to say thank you for all the wonderful libraries, including HATEOAS, you guys have created for the community over the years!

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

No branches or pull requests

2 participants