2

Front-End dev here working on my first Java Spring Boot API. I've been reading many articles on the "best practices" in Spring/Spring Boot and have been attempting to refactor my code to follow those practices.

Below I have an example of a generic class I use to handle all HTTP requests for my various services. Originally I had this class annotated with the @Component annontation, but as I mentioned I am trying to learn and follow Spring "best practices." In particular I am interested in implementing what this article on best practices describes (Number 3 & 4 in the article). That says one should avoid using @component, because we don't want to be tightly coupled to the Spring framework and we want to avoid "entire class path scanning."

@Slf4j
public class HttpService {

  private HttpServletRequest request;
  private RestTemplate restTemplate;

  public HttpService(HttpServletRequest request, RestTemplateBuilder restTemplateBuilder) { ... }

  public String get(String url, String id) { ... }
}

With the @component annotation my service works as expected, but when I remove it I get the exception:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type

This seems to be a pretty common exception in Java as there is a LOT of questions about it, but those solutions have not worked for me.

My question I'm hoping the community can help me answer is two part:

  1. How do I correctly make this a useable generic class that is not tightly coupled to Spring?
  2. Is this actually a correct approach for Spring development or am I reading too deeply into this article?
Narm
  • 10,677
  • 5
  • 41
  • 54
  • What do you mean by "handle HTTP requests"? At some point, you have to decide that you're coupling _something_ and go with it. (Note that in this case, you might be able to simply use `@Import(HttpService.class)`.) – chrylis -cautiouslyoptimistic- Aug 22 '19 at 19:49
  • The other services I have need to make various CRUD calls. So instead of injecting the `RestTemplate` in all my services and handling all the possible HTTP responses and exceptions in all the various services I wanted to avoid the code duplication and create one generic HttpService that handles all the potential responses that comes with making Http calls and just have my services use the HttpService. It's just much cleaner that way and makes sense from a responsibility/separation of concerns stand point, at least to me anyways. – Narm Aug 22 '19 at 19:55
  • 1
    Don't invent extra levels of abstraction _solely_ for the purpose of "avoiding coupling". If you're summarizing the REST requests in some sort of API written in business terms, great, but don't write an entire framework full of delegates. In the real world, nobody is going to want to swap out REST clients badly enough for you to pay this expensive cost now. – chrylis -cautiouslyoptimistic- Aug 22 '19 at 19:58
  • Thank you for your responses and time @chrylis. Andrew Tobilko ,Turing85, and yourself all made good points. I believe it's partially a misunderstanding on my part as to the appropriate context in which these "best practices" are applied and simply a bad case of trying to "decouple for the sake of decoupling". I definitely don't want to make things more complicated for no real benefit. – Narm Aug 22 '19 at 21:40

3 Answers3

5

That says one should avoid using @Component because we don't want to be tightly coupled to the Spring framework ...

It says we don't want our domain classes to be tightly coupled to Spring. There is a difference between HttpService (which is bound to the web context and depends on Spring's RestTemplateBuilder) and MySuperSpecificDomainCalculator (which should keep working regardless of the context it is put in).

... and we want to avoid "entire classpath scanning."

I see nothing extremely evil in using classpath scanning. You may let Spring scan a small set of packages. You may point exactly to where your Spring-dependent classes reside. A @Configuration with @Bean methods is an alternative, and sometimes the only one.

How do I correctly make this a useable generic class that is not tightly coupled to Spring?

You are designing a web layer. You are choosing a technology to use. You decided to go with Spring Web. At some point, you will have to bind Spring's classes to yours. As you already did. HttpService is dependent on Spring regardless of the annotation. It takes RestTemplateBuilder which is a Spring class.

And there is nothing wrong. You need to write such classes to integrate the framework into your application and to make it work for you. Just make sure the line between the Spring world and your domain world is well-defined.

Is this actually a correct approach for Spring development or am I reading too deeply into this article?

The article is reasonable. Your HttpService looks a valid @Component to me. Don't overthink it.

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • Thank you for your response, you make very valid points. I believe you and @turing85 are right. I definitely had a bit of a lack of understanding as to what context the "best practices" should be applied and what exactly is considered a domain class. – Narm Aug 22 '19 at 21:45
2

Looking at the article you have linked, I think there is a misunderstanding on the term "domain". The "domain" is where the application generates its value. Taking Steam as an example, the capability to search and buy games generates (business) value. A HTTP service, on the other hand, is a technical detail. It is necessary, yes, but generates no value on its own. So point 3 and 4 of the article do not apply to the HttpService.

In a typical web application, you have at least three layer:

  • One layer providing the the API: those are your endpoints and all objects that are used by the API. This includes, for example, entities representing the requests and responses passed through/returned by the endpoints.
  • One layer providing Persistence: Typically, these are repositories, DAOs, or whatever persistence model you want to use. Again, everything needed by the persistence unit should be included in this layer.
  • One layer holding the actual domain: this is what we talk about when we talk about the "domain". Here we see what the application actually does.

Now comes the tricky part: The web and the database are nothing else than I/O channels. they could be a CLI and some files, HTTP requests/responses and a database or whatever will be developed in the future. A good software design allows for easy swapping of the I/O channel, while preserving the domain-specific code. You see this in many applications:

  • OpenShift has a REST API and a CLI
  • AWS has a REST API and several CLIs, many are 3rd party
  • Steam has a web storefront, a desktop app and a mobile client
  • Keycloak allows pulling authentication information from different sources (LDAP, OAuth2/OIDC, database,...)

But to get this flexibility, we have to segregate somewhere. What is a technicality? What is domain? How clean do I want/have to decouple? In your concrete case: Does it matter whether you bind yourself to spring-boot within your service?

To achieve such an architecture, we use patterns and principles. A set of well-known principles are the SOLID-principles. How strict one should follow those principles is a personal matter everyone has to decide for her-/himself. My personal opinion is that a pragmatic approach is often sufficient.

I also want to pick up @chrylis comment on abstraction. We as software engineers like to indulge ourselves in technicalities and create a superawesome abstraction that can basically deal with everything. But that is not the point. It does not have to deal with everything, only with what it will be used for. If you work for clients: that is not what the client is paying you for. And abstraction always comes at a cost. In most cases, this cost is complexity and/or readability. Or in words from wo*men wiser than me: KISS and YAGNI

Turing85
  • 18,217
  • 7
  • 33
  • 58
  • Thank you for your time and response. Definitely was a little unclear on what the domain classes are and how to appropriately apply these "best practices" and you helped clear that up quite well. – Narm Aug 22 '19 at 21:47
0

Adding @Component to your class forces anyone who uses your class to know about Spring (i.e. spring is a compile time dependency).

The easiest alternative is to create separate class annotated with @Configuration in your app, and let it handle creating your class a Spring bean.

For example:

@Configuration
public class MyConfiguration {
    @Bean
    public HttpService httpService() {
        return new HttpService();
    }
}

This keeps your HttpService class free from Spring dependencies (assuming it doesn't use any other Spring annotations such as @Autowired), but lets it behave as a Spring bean in your own application.

Note that your class still depends on RestTemplateBuilder, which itself is a Spring boot class, which means your class (and anyone who uses it) will require Spring.

Mike
  • 4,722
  • 1
  • 27
  • 40
  • 1
    Annotations don't have to be on the consumer's classpath; they're just silently ignored if not included. – chrylis -cautiouslyoptimistic- Aug 22 '19 at 19:59
  • At runtime that is true. But simply having that import in the class makes Spring a _compile_ time dependency as I stated. – Mike Aug 22 '19 at 20:15
  • You're mixing meanings of the term *compile*. It's *compile-time*, but not *compile-scoped*; it can be either optional or provided. – chrylis -cautiouslyoptimistic- Aug 22 '19 at 20:20
  • If you import a class, the compiler has to load it. Even if the dependency scope is provided, the JDK will require a class definition to build your own byte code. – Mike Aug 22 '19 at 20:24
  • None of which requires *consumers* of your class to know about `Component`. – chrylis -cautiouslyoptimistic- Aug 22 '19 at 20:27
  • Unless that consumer is another developer working on your code. – Mike Aug 22 '19 at 20:31
  • 1
    I don't think this is a bad answer, and upvoted it. The accepted answer, which I agree with and also upvoted, is a practical opinion of Spring annotations and when/if to worry about them being in your classes. But the question was about how to keep the annotations out of the class source code, and this is one way to do that. Usually configuration classes are for instantiating library classes that you can't annotate, but there's nothing strictly wrong with doing it for your own code. You could argue it centralizes the use of the annotations. My two cents, and reasonable minds will disagree. – RichW Aug 23 '19 at 20:42