Working with Spring Boot (but also working with JEE/Jakarta EE) can be a joyful experience. Within minutes you can create a new RESTfull webservice to which you can quickly and easily add circuit breakers, distributed tracing, security, database access, accessing other microservices, fallbacks, retries, service discovery, configuration management, many other things and of course you can use the great Spring DI framework.
Most of the above features are enabled by either just adding a dependency, adding some annotations, adding some @Configuration classes or just by setting a property.
And there is also a range of options. E.g. you can use Tomcat, Jetty, Undertow, or Netty as your http server. You can use SpringMVC using synchronized or asynchronized requests. You can also use the reactive Spring webFlux framework. On top of the webFlux framework you can also use the Functional Web Framework.
Most power of Spring comes from AOP (Aspect Oriented Programming). You tag all your components with annotations and Spring wires all together, and is therefore able to add wrappers around your components to add cross cutting features.
The AOP Pitfall
There is a drawback of using AOP.
Almost all of the Spring logic lies in the AOP wrappers, and from a Spring user perspective, this all looks like magic.
If you ever see a stacktrace of any function within your application, you see a lot of spring methods between all your call’s. It is hard to understand what exactly is happening.
When you application is running fine, then there is no problem, but what if your rest endpoint is never called while you would expect that it should. Where do you put a breakpoint? It could be a authentication issue, but can you debug that? If you are not familiar with the application that you are working with, then you have to search which properties are used, which dependencies are added and search the application for any configuration classes to find out how authentication is added.
The ‘Spring is too big to understand’ pitfall
There is so much you can do with Spring that it is almost impossible to get to the same level of Spring knowledge of Josh Long. The documentation is overwhelming, but you mostly just need to find out the one thing that is hard to find.
There are so many annotations, configurations, settings and modules, that you cannot just read the complete documentation and know what all is possible.
Most people google a while, try everything they find on stackoverflow and stick with something that works and seems correct. Mostly not fully understanding it.
This can be very frustrating and unfortunately happens to often,
The upgrade pitfall
Another catch is when you need to pickup an application that was created a few years ago using an older version of Spring. The first thing you often want to do is to update all dependencies of the project, which means upgrading spring also. You could spend a lot of time figuring out if all properties, annotations and configurations are still working the same as they did in the previous version. When the application is new for you too, then it is hard to know if some properties or configurations are still needed in the new version.
It is also very likely that you use one of the transitive dependencies that Spring delivers. There are updated to, so you need to look at these to.
You cannot update one library at the time. All libraries are updated at the same time.
The ‘clean architecture’ pitfall
Uncle Bob says: frameworks and libraries should be a detail.
That means, that whatever library you use for handling your REST call’s, should be hidden in your rest module, and not ‘leak’ into any other module.
With Spring this is very difficult, if not impossible, mainly because of the annotations based DI which you are kind of forced to use.
I find it very nice to be able to swap the framework or library that handles my REST call’s without worrying that it would affect other parts of the system.
Sidewalk: looking at today’s microservices
Today, a lot of microservices run on kubernetis.
Lately you can add a service mesh like istio to that.
A service mesh can take care of:
- Service Discovery
- Load balancing
- Circuit breaker
- Distributed tracing
This means, it will release a lot of issues away from the microservice itself.
What is still the value of Spring (boot) today?
Even with a service mesh in place, Spring still offers a lot of functionality as described in the first parts of this blog.
However, if we try, we can use other libraries for that also, that does not use AOP based annotations.
Building an application with alternative libraries
Let’s try to build a similar application as you can create with Spring, but then with small dedicated libraries.
I created a github project which contains multiple subprojects each focussing on a single aspect of an application. This project can be found here: https://github.com/robbertvdzon/javalinsamples
Below is an overview of all aspects that are handled. For details: check the github project.
I found that every single aspect was very easy to be added, and easy to understand.
Basic web aspects(all using only javalin):
- Basic REST application
- Rest application with basic authentication
- Rest application with mapping exceptions
- Rest application with health checks
- Rest application with https support
- Application running as a runnable jar
- Application serving static files
- Application serving thymeleaf templates (javalin with thymeleaf)
Other web aspects:
- Application with a rest client, including circuit breakers / retry / fallback (with apache httpclient and failsafe)
- Application with logging in kibana format(using Log4j)
- Application with an SQL client (using Hybernate)
- Application using dependency injection (using Dagger2)
- Application with a yaml configuration file (using snakeyml)
- Rest application with bean validation (using hybernate validator)
- Application with scheduled jobs (using quartz)
Consumer Driven Contract
- Application with a Consumer Driven Contract (using spring cloud contract)
- Application with prometheus metrix (using jmx_exporter)